/*
* Copyright 2016-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.intellij.ideabuck.debugger;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.Client;
import com.android.ddmlib.IDevice;
import com.facebook.buck.intellij.ideabuck.config.BuckSettingsProvider;
import com.intellij.execution.ProgramRunnerUtil;
import com.intellij.execution.RunManager;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.execution.remote.RemoteConfiguration;
import com.intellij.execution.remote.RemoteConfigurationType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NonNls;
public class AndroidDebugger {
// Have the maximum retry time of 1 second
private static final int MAX_RETRIES = 8;
private static final int RETRY_TIME = 10;
private static final long ADB_CONNECT_TIMEOUT_MS = 5000;
private static final long ADB_CONNECT_TIME_STEP_MS = ADB_CONNECT_TIMEOUT_MS / 10;
@NonNls private static final String RUN_CONFIGURATION_NAME_PATTERN = "Android Debugger (%s)";
private AndroidDebugger() {}
public static void init() throws InterruptedException {
if (AndroidDebugBridge.getBridge() == null
|| !isAdbInitialized(AndroidDebugBridge.getBridge())) {
AndroidDebugBridge.initIfNeeded(/* clientSupport */ true);
AndroidDebugBridge.createBridge(
BuckSettingsProvider.getInstance().getState().adbExecutable, false);
}
long start = System.currentTimeMillis();
while (!isAdbInitialized(AndroidDebugBridge.getBridge())) {
long timeLeft = start + ADB_CONNECT_TIMEOUT_MS - System.currentTimeMillis();
if (timeLeft <= 0) {
break;
}
Thread.sleep(ADB_CONNECT_TIME_STEP_MS);
}
}
private static boolean isAdbInitialized(AndroidDebugBridge adb) {
return adb.isConnected() && adb.hasInitialDeviceList();
}
public static void disconnect() {
if (AndroidDebugBridge.getBridge() != null
&& isAdbInitialized(AndroidDebugBridge.getBridge())) {
AndroidDebugBridge.disconnectBridge();
AndroidDebugBridge.terminate();
}
}
public static void attachDebugger(final String packageName, final Project project)
throws InterruptedException {
IDevice[] devices = AndroidDebugBridge.getBridge().getDevices();
if (devices.length == 0) {
return;
}
IDevice device = devices[0];
Client client;
int currentRetryNumber = 0;
int currentRetryTime = RETRY_TIME;
// It takes some time to get the updated client process list, so wait for it
do {
client = device.getClient(packageName);
currentRetryTime *= 2;
Thread.sleep(currentRetryTime);
currentRetryNumber++;
} while (client == null && currentRetryNumber < MAX_RETRIES);
if (client == null) {
throw new RuntimeException(
"Connecting to the adb debug server timed out."
+ "Can't find package with name "
+ packageName
+ ".");
}
String debugPort = String.valueOf(client.getDebuggerListenPort());
final RunnerAndConfigurationSettings settings = createRunConfiguration(project, debugPort);
ApplicationManager.getApplication()
.invokeLater(
new Runnable() {
@Override
public void run() {
// Needs read access which is available on the read thread.
ProgramRunnerUtil.executeConfiguration(
project, settings, DefaultDebugExecutor.getDebugExecutorInstance());
}
});
}
private static RunnerAndConfigurationSettings createRunConfiguration(
Project project, String debugPort) {
final RemoteConfigurationType remoteConfigurationType = RemoteConfigurationType.getInstance();
final ConfigurationFactory factory = remoteConfigurationType.getFactory();
final RunnerAndConfigurationSettings runSettings =
RunManager.getInstance(project)
.createRunConfiguration(getRunConfigurationName(debugPort), factory);
final RemoteConfiguration configuration = (RemoteConfiguration) runSettings.getConfiguration();
configuration.HOST = "localhost";
configuration.PORT = debugPort;
configuration.USE_SOCKET_TRANSPORT = true;
configuration.SERVER_MODE = false;
return runSettings;
}
private static String getRunConfigurationName(String debugPort) {
return String.format(RUN_CONFIGURATION_NAME_PATTERN, debugPort);
}
}