blob: 6356e5ebd114c052abe3ab1075ab69624d2dc6a5 [file] [log] [blame]
/*
* 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 org.jetbrains.android.run;
import com.android.annotations.VisibleForTesting;
import com.android.ddmlib.*;
import com.intellij.execution.configurations.RuntimeConfigurationException;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.openapi.diagnostic.Logger;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import static com.intellij.execution.process.ProcessOutputTypes.STDERR;
import static com.intellij.execution.process.ProcessOutputTypes.STDOUT;
public class AndroidActivityLauncher extends AndroidApplicationLauncher {
private static final Logger LOG = Logger.getInstance(AndroidActivityLauncher.class);
@NotNull private final AndroidFacet myFacet;
private final boolean myNeedsLaunch;
@NotNull private final ActivityLocator myActivityLocator;
@NotNull private final String myActivityExtraFlags;
public AndroidActivityLauncher(@NotNull AndroidFacet facet, boolean needsLaunch, @NotNull ActivityLocator locator, @NotNull String activityExtraFlags) {
myFacet = facet;
myNeedsLaunch = needsLaunch;
myActivityLocator = locator;
myActivityExtraFlags = activityExtraFlags;
}
public void checkConfiguration() throws RuntimeConfigurationException {
try {
myActivityLocator.validate();
}
catch (ActivityLocator.ActivityLocatorException e) {
throw new RuntimeConfigurationException(e.getMessage());
}
}
@Override
public boolean isReadyForDebugging(@NotNull ClientData data, @Nullable ProcessHandler processHandler) {
ClientData.DebuggerStatus status = data.getDebuggerConnectionStatus();
switch (status) {
case ERROR:
if (processHandler != null) {
processHandler.notifyTextAvailable("Debug port is busy\n", STDOUT);
}
LOG.info("Debug port is busy");
return false;
case ATTACHED:
if (processHandler != null) {
processHandler.notifyTextAvailable("Debugger already attached\n", STDOUT);
}
LOG.info("Debugger already attached");
return false;
case WAITING:
return true;
case DEFAULT:
default:
String msg = "Client not ready yet.";
if (processHandler != null) {
processHandler.notifyTextAvailable(msg + "\n", STDOUT);
}
LOG.info(msg);
return false;
}
}
@Override
public LaunchResult launch(@NotNull AndroidRunningState state, @NotNull IDevice device)
throws IOException, AdbCommandRejectedException, TimeoutException {
if (!myNeedsLaunch) {
return LaunchResult.NOTHING_TO_DO;
}
ProcessHandler processHandler = state.getProcessHandler();
String activityName;
try {
activityName = myActivityLocator.getQualifiedActivityName(device);
}
catch (ActivityLocator.ActivityLocatorException e) {
processHandler.notifyTextAvailable("Could not identify launch activity: " + e.getMessage(), STDOUT);
return LaunchResult.NOTHING_TO_DO;
}
final String activityPath = getLauncherActivityPath(state.getPackageName(), activityName);
if (state.isStopped()) return LaunchResult.STOP;
processHandler.notifyTextAvailable("Launching application: " + activityPath + ".\n", STDOUT);
AndroidRunningState.MyReceiver receiver = state.new MyReceiver();
while (true) {
if (state.isStopped()) return LaunchResult.STOP;
String command = getStartActivityCommand(activityPath, getDebugFlags(state), myActivityExtraFlags);
boolean deviceNotResponding = false;
try {
state.executeDeviceCommandAndWriteToConsole(device, command, receiver);
}
catch (ShellCommandUnresponsiveException e) {
LOG.info(e);
deviceNotResponding = true;
}
if (!deviceNotResponding && receiver.getErrorType() != 2) {
break;
}
processHandler.notifyTextAvailable("Device is not ready. Waiting for " + AndroidRunningState.WAITING_TIME_SECS + " sec.\n", STDOUT);
synchronized (state.getRunningLock()) {
try {
state.getRunningLock().wait(AndroidRunningState.WAITING_TIME_SECS * 1000);
}
catch (InterruptedException e) {
}
}
receiver = state.new MyReceiver();
}
boolean success = receiver.getErrorType() == AndroidRunningState.NO_ERROR;
if (success) {
processHandler.notifyTextAvailable(receiver.getOutput().toString(), STDOUT);
return LaunchResult.SUCCESS;
}
else {
processHandler.notifyTextAvailable(receiver.getOutput().toString(), STDERR);
return LaunchResult.STOP;
}
}
@VisibleForTesting
@NotNull
static String getStartActivityCommand(@NotNull String activityPath, @NotNull String debugFlags, @NotNull String extraFlags) {
return "am start " +
debugFlags +
" -n \"" + activityPath + "\" " +
"-a android.intent.action.MAIN " +
"-c android.intent.category.LAUNCHER" +
(extraFlags.isEmpty() ? "" : " " + extraFlags);
}
@VisibleForTesting
@NotNull
static String getLauncherActivityPath(@NotNull String packageName, @NotNull String activityName) {
return packageName + "/" + activityName.replace("$", "\\$");
}
/** Returns the flags used to the "am start" command for launching in debug mode. */
@NotNull
protected String getDebugFlags(@NotNull AndroidRunningState state) {
return state.isDebugMode() ? "-D" : "";
}
}