/*
* Copyright (c) 2014, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.google.dart.tools.debug.core.mobile;
import com.google.dart.engine.utilities.instrumentation.InstrumentationBuilder;
import com.google.dart.tools.core.mobile.AndroidDebugBridge;
import com.google.dart.tools.core.mobile.AndroidDevice;
import com.google.dart.tools.core.mobile.MobileUrlConnectionException;
import com.google.dart.tools.debug.core.DartDebugCorePlugin;
import com.google.dart.tools.debug.core.DartLaunchConfigWrapper;
import com.google.dart.tools.debug.core.DartLaunchConfigurationDelegate;
import com.google.dart.tools.debug.core.DebugUIHelper;
import com.google.dart.tools.debug.core.pubserve.PubCallback;
import com.google.dart.tools.debug.core.pubserve.PubResult;
import com.google.dart.tools.debug.core.pubserve.PubServeManager;
import com.google.dart.tools.debug.core.pubserve.PubServeResourceResolver;
import com.google.dart.tools.debug.core.util.BrowserManager;
import com.google.dart.tools.debug.core.util.IRemoteConnectionDelegate;
import com.google.dart.tools.debug.core.util.IResourceResolver;
import com.google.dart.tools.debug.core.util.ResourceServer;
import com.google.dart.tools.debug.core.util.ResourceServerManager;
import com.google.dart.tools.debug.core.webkit.ChromiumTabInfo;
import com.google.dart.tools.debug.core.webkit.DefaultChromiumTabChooser;
import com.google.dart.tools.debug.core.webkit.IChromiumTabChooser;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.IDebugTarget;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
/**
* A LaunchConfigurationDelegate to launch in the browser on a connected device.
*/
public class MobileLaunchConfigurationDelegate extends DartLaunchConfigurationDelegate implements
IRemoteConnectionDelegate {
/**
* A class to choose a tab from the given list of tabs.
*/
public static class ChromeTabChooser implements IChromiumTabChooser {
public ChromeTabChooser() {
}
@Override
public ChromiumTabInfo chooseTab(final List<ChromiumTabInfo> tabs) {
if (tabs.size() == 0) {
return null;
}
int tabCount = 0;
for (ChromiumTabInfo tab : tabs) {
if (!tab.isChromeExtension()) {
tabCount++;
}
}
if (tabCount == 1) {
return tabs.get(0);
}
for (ChromiumTabInfo tab : tabs) {
if (!tab.isChromeExtension()) {
return tab;
}
}
return new DefaultChromiumTabChooser().chooseTab(tabs);
}
}
private PubCallback<String> pubConnectionCallback = new PubCallback<String>() {
@Override
public void handleResult(PubResult<String> result) {
if (result.isError()) {
DebugUIHelper.getHelper().showError(
"Launch Error",
"Pub serve communication error: " + result.getErrorMessage());
return;
}
try {
String launchUrl = result.getResult();
launchOnMobile(launchUrl, true, new NullProgressMonitor());
} catch (MobileUrlConnectionException e) {
// DartDebugCorePlugin.logError(e);
DebugUIHelper.getHelper().showError("Dartium Launch Error", e);
} catch (CoreException e) {
DartDebugCorePlugin.logError(e);
DebugUIHelper.getHelper().showError("Dartium Launch Error", e);
}
}
};
private DartLaunchConfigWrapper wrapper;
private static final int REMOTE_DEBUG_PORT = 9224;
private String mode;
@Override
public void doLaunch(ILaunchConfiguration configuration, String mode, ILaunch launch,
IProgressMonitor monitor, InstrumentationBuilder instrumentation) throws CoreException {
wrapper = new DartLaunchConfigWrapper(configuration);
wrapper.markAsLaunched();
this.mode = mode;
String launchUrl = "";
boolean usePubServe = wrapper.getUsePubServe();
if (wrapper.getShouldLaunchFile()) {
IResource resource = wrapper.getApplicationResource();
if (resource == null) {
throw new CoreException(new Status(
IStatus.ERROR,
DartDebugCorePlugin.PLUGIN_ID,
"Html file could not be found"));
}
instrumentation.metric("Resource-Class", resource.getClass().toString());
instrumentation.data("Resource-Name", resource.getName());
try {
if (usePubServe) {
PubServeManager manager = PubServeManager.getManager();
try {
manager.serve(wrapper, pubConnectionCallback);
} catch (Exception e) {
throw new CoreException(new Status(
IStatus.ERROR,
DartDebugCorePlugin.PLUGIN_ID,
"Could not start pub serve or connect to pub\n" + manager.getStdErrorString(),
e));
}
} else {
launchUrl = getUrlFromResourceServer(resource);
launchOnMobile(launchUrl, usePubServe, monitor);
}
} catch (Exception e) {
throw new CoreException(new Status(
IStatus.ERROR,
DartDebugCorePlugin.PLUGIN_ID,
"Unable to launch on device: " + e.getLocalizedMessage(),
e));
}
} else {
launchUrl = wrapper.getUrl();
try {
String scheme = new URI(launchUrl).getScheme();
if (scheme == null) { // add scheme else browser will not launch
launchUrl = "http://" + launchUrl;
launchOnMobile(launchUrl, usePubServe, monitor);
}
} catch (URISyntaxException e) {
throw new CoreException(new Status(
IStatus.ERROR,
DartDebugCorePlugin.PLUGIN_ID,
"Error in specified url"));
}
}
DebugPlugin.getDefault().getLaunchManager().removeLaunch(launch);
}
@Override
public IDebugTarget performRemoteConnection(String host, int port, IContainer container,
IProgressMonitor monitor, boolean usePubServe) throws CoreException {
BrowserManager browserManager = new BrowserManager();
IResourceResolver resolver = null;
try {
resolver = usePubServe ? new PubServeResourceResolver() : ResourceServerManager.getServer();
} catch (IOException e) {
return null;
}
return browserManager.performRemoteConnection(
new ChromeTabChooser(),
host,
port,
monitor,
resolver);
}
protected void launchOnMobile(String launchUrl, boolean usePubServe, IProgressMonitor monitor)
throws CoreException {
AndroidDebugBridge devBridge = AndroidDebugBridge.getAndroidDebugBridge();
devBridge.startAdbServer();
AndroidDevice device = devBridge.getConnectedDevice();
if (device == null) {
throw new CoreException(new Status(
IStatus.ERROR,
DartDebugCorePlugin.PLUGIN_ID,
"No devices detected.\n\nConnect device, enable USB debugging and try again."));
}
if (device.getDeviceId() == null) {
throw new CoreException(new Status(
IStatus.ERROR,
DartDebugCorePlugin.PLUGIN_ID,
"Unauthorized device detected.\n\nAuthorize device and try again."));
}
if (wrapper.getInstallContentShell()) {
if (!devBridge.installContentShellApk(device)) {
throw new CoreException(new Status(
IStatus.ERROR,
DartDebugCorePlugin.PLUGIN_ID,
"Failed to install content shell on mobile"));
}
}
devBridge.launchContentShell(device.getDeviceId(), launchUrl);
if (!devBridge.isHtmlPageAccessible(device, launchUrl).isOK()) {
// pub serve is always localhost over USB while old dev server is always over wifi
boolean localhostOverUsb = usePubServe;
throw new MobileUrlConnectionException(launchUrl, localhostOverUsb);
}
if (ILaunchManager.DEBUG_MODE.equals(mode)) {
// check if remote connection is alive
if (!isRemoteConnected()) {
devBridge.setupPortForwarding(Integer.toString(REMOTE_DEBUG_PORT));
performRemoteConnection("localhost", REMOTE_DEBUG_PORT, null, monitor, usePubServe);
}
}
}
private String getUrlFromResourceServer(IResource resource) throws IOException, CoreException,
URISyntaxException {
ResourceServer server = ResourceServerManager.getServer();
String resPath = resource.getFullPath().toPortableString();
String localAddress = server.getLocalAddress();
if (localAddress == null) {
throw new MobileUrlConnectionException(null, false);
}
URI uri = new URI("http", null, localAddress, server.getPort(), resPath, null, null);
String launchUrl = uri.toString();
launchUrl = wrapper.appendQueryParams(launchUrl);
return launchUrl;
}
private boolean isRemoteConnected() {
IDebugTarget[] targets = DebugPlugin.getDefault().getLaunchManager().getDebugTargets();
for (IDebugTarget target : targets) {
try {
if (target.getName().equals("Remote") && !target.isTerminated()) {
return true;
}
} catch (DebugException e) {
}
}
return false;
}
}