/* * 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.ui.internal.mobile; import com.google.dart.tools.core.mobile.AndroidDebugBridge; import com.google.dart.tools.core.mobile.AndroidDevice; import com.google.dart.tools.core.model.DartSdkManager; import com.google.dart.tools.debug.core.DartLaunchConfigWrapper; import com.google.dart.tools.debug.ui.internal.DartDebugUIPlugin; import com.google.dart.tools.debug.ui.internal.util.LaunchTargetComposite; import com.google.dart.tools.ui.internal.util.ExternalBrowserUtil; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Listener; import java.util.concurrent.atomic.AtomicBoolean; /** * Main tab for Mobile launch configurations */ public class MobileMainTab extends AbstractLaunchConfigurationTab { private static final String DEVICE_NOT_AUTHORIZED = "Connected mobile is not authorized"; private static final String DEVICE_NOT_FOUND = "No mobile found or USB development not enabled on mobile"; public static final String MOBILE_DOC_URL = "https://www.dartlang.org/tools/editor/mobile.html"; // PORT_FORWARD_DOC_URL should be #set-up-port-forwarding but is #connect-the-devices // until dartbug.com/19457 is fixed. public static final String PORT_FORWARD_DOC_URL = MOBILE_DOC_URL + "#connect-the-devices"; private static final String INFO_TEXT = "Pub-Serve runs on your local machine, and serves your application over the USB cable." + "To use it you have to <a href=\"" + PORT_FORWARD_DOC_URL + "\">setup port forwarding</a> so that your mobile can see the server."; private static final String SERVER_INFO_TEXT = "Use the server embedded in the Dart Editor, " + "connecting over Wifi. This requires that your phone can establish a direct connection by Wifi " + "to the computer where Dart Editor is running."; // When these change, be sure to change the messaging in MobileUrlConnectionException private String[] servers = {"Embedded server over WiFi network", "Pub Serve over USB"}; private LaunchTargetComposite launchTargetGroup; private AndroidDevice connectedDevice = null; private final AtomicBoolean monitorDeviceConnection = new AtomicBoolean(false); private Link infoLink; private Combo serversCombo; @Override public void createControl(Composite parent) { Composite composite = new Composite(parent, SWT.NONE); GridLayoutFactory.swtDefaults().spacing(1, 3).applyTo(composite); launchTargetGroup = new LaunchTargetComposite(composite, SWT.NONE); launchTargetGroup.addListener(SWT.Modify, new Listener() { @Override public void handleEvent(Event event) { notifyPanelChanged(); } }); launchTargetGroup.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { stopMonitorDeviceConnectionInBackground(); } }); // pub serve setting Group group = new Group(composite, SWT.NONE); group.setText("Server"); GridDataFactory.fillDefaults().grab(true, true).applyTo(group); GridLayoutFactory.swtDefaults().numColumns(2).applyTo(group); ((GridLayout) group.getLayout()).marginBottom = 5; ((GridLayout) group.getLayout()).verticalSpacing = 10; serversCombo = new Combo(group, SWT.DROP_DOWN | SWT.READ_ONLY); serversCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { handleComboChanged(serversCombo.getSelectionIndex() == 1); } }); serversCombo.setItems(servers); Label separator = new Label(group, SWT.WRAP); GridDataFactory.swtDefaults().grab(true, false).span(1, 2).applyTo(separator); infoLink = new Link(group, SWT.WRAP); infoLink.setText(INFO_TEXT); infoLink.addSelectionListener(new SelectionAdapter() { @Override public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } @Override public void widgetSelected(SelectionEvent e) { ExternalBrowserUtil.openInExternalBrowser(e.text.trim()); } }); GridDataFactory.swtDefaults().span(2, 1).grab(true, false).hint(415, SWT.DEFAULT).applyTo( infoLink); new Label(group, SWT.NONE); setControl(composite); } @Override public void dispose() { Control control = getControl(); if (control != null) { control.dispose(); setControl(null); } } @Override public String getErrorMessage() { if (performSdkCheck() != null) { return performSdkCheck(); } if (connectedDevice == null) { return DEVICE_NOT_FOUND; } if (!connectedDevice.isAuthorized()) { return DEVICE_NOT_AUTHORIZED; } return launchTargetGroup.getErrorMessage(); } /** * Answer the image to show in the configuration tab or <code>null</code> if none */ @Override public Image getImage() { return DartDebugUIPlugin.getImage("android.png"); //$NON-NLS-1$ } @Override public String getMessage() { return "Create a configuration to launch a Dart application on a device." + "This installs a browser with the Dart VM on the device and launches the app in it."; } /** * Answer the name to show in the configuration tab */ @Override public String getName() { return "Main"; } /** * Initialize the UI from the specified configuration */ @Override public void initializeFrom(ILaunchConfiguration config) { DartLaunchConfigWrapper wrapper = new DartLaunchConfigWrapper(config); launchTargetGroup.setHtmlTextValue(wrapper.appendQueryParams(wrapper.getApplicationName())); launchTargetGroup.setUrlTextValue(wrapper.getUrl()); launchTargetGroup.setSourceDirectoryTextValue(wrapper.getSourceDirectoryName()); if (wrapper.getShouldLaunchFile()) { launchTargetGroup.setHtmlButtonSelection(true); } else { launchTargetGroup.setHtmlButtonSelection(false); } if (wrapper.getUsePubServe()) { serversCombo.select(1); handleComboChanged(true); } else { serversCombo.select(0); handleComboChanged(false); } startMonitorDeviceConnectionInBackground(launchTargetGroup.getDisplay()); } /** * Store the value specified in the UI into the launch configuration */ @Override public void performApply(ILaunchConfigurationWorkingCopy config) { DartLaunchConfigWrapper wrapper = new DartLaunchConfigWrapper(config); wrapper.setShouldLaunchFile(launchTargetGroup.getHtmlButtonSelection()); String fileUrl = launchTargetGroup.getHtmlFileName(); if (fileUrl.indexOf('?') == -1) { wrapper.setApplicationName(fileUrl); wrapper.setUrlQueryParams(""); } else { int index = fileUrl.indexOf('?'); wrapper.setApplicationName(fileUrl.substring(0, index)); wrapper.setUrlQueryParams(fileUrl.substring(index + 1)); } wrapper.setUrl(launchTargetGroup.getUrlString()); wrapper.setSourceDirectoryName(launchTargetGroup.getSourceDirectory()); wrapper.setUsePubServe(serversCombo.getSelectionIndex() == 1); } @Override public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { DartLaunchConfigWrapper wrapper = new DartLaunchConfigWrapper(configuration); wrapper.setShouldLaunchFile(true); wrapper.setApplicationName(""); //$NON-NLS-1$ wrapper.setLaunchContentShell(true); wrapper.setUsePubServe(true); } protected void handleComboChanged(boolean usePubServe) { if (usePubServe) { infoLink.setText(INFO_TEXT); } else { infoLink.setText(SERVER_INFO_TEXT); } notifyPanelChanged(); } private void notifyPanelChanged() { setDirty(true); updateLaunchConfigurationDialog(); } private String performSdkCheck() { if (!DartSdkManager.getManager().hasSdk()) { return "Dart SDK is not installed (" + DartSdkManager.getManager().getSdk().getDart2JsExecutable() + ")"; } else { return null; } } /** * Start the background process that monitors device connection via ADB */ private void startMonitorDeviceConnectionInBackground(final Display display) { if (!monitorDeviceConnection.get()) { monitorDeviceConnection.set(true); Thread thread = new Thread("Monitor mobile connection") { @Override public void run() { AndroidDebugBridge devBridge = AndroidDebugBridge.getAndroidDebugBridge(); AndroidDevice oldDevice = devBridge.getConnectedDevice(); update(oldDevice); while (monitorDeviceConnection.get()) { AndroidDevice newDevice = devBridge.getConnectedDevice(); if (!AndroidDevice.isEqual(oldDevice, newDevice)) { oldDevice = newDevice; update(oldDevice); } try { Thread.sleep(1000); } catch (InterruptedException e) { //$FALL-THROUGH$ } } } private void update(final AndroidDevice device) { display.asyncExec(new Runnable() { @Override public void run() { updateMobileStatus(device); } }); } }; thread.setDaemon(true); thread.start(); } } /** * Stop the background process that monitors device connection via ADB */ private void stopMonitorDeviceConnectionInBackground() { monitorDeviceConnection.set(false); } /** * Update the mobile status. Must be called on the UI thread. * * @param isDeviceConnected {@code true} if a mobile device is currently connected */ private void updateMobileStatus(AndroidDevice device) { connectedDevice = device; updateLaunchConfigurationDialog(); } }