| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.netbeans.modules.cordova.platforms.android; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.net.Proxy; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.nio.channels.SelectionKey; |
| import java.nio.charset.Charset; |
| import java.util.concurrent.TimeUnit; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import org.json.simple.JSONArray; |
| import org.json.simple.JSONObject; |
| import org.json.simple.JSONValue; |
| import org.json.simple.parser.JSONParser; |
| import org.json.simple.parser.ParseException; |
| import org.netbeans.modules.cordova.platforms.spi.MobileDebugTransport; |
| import org.netbeans.modules.cordova.platforms.api.ProcessUtilities; |
| import org.netbeans.modules.cordova.platforms.api.WebKitDebuggingSupport; |
| import org.netbeans.modules.netserver.api.ProtocolDraft; |
| import org.netbeans.modules.netserver.api.WebSocketClient; |
| import org.netbeans.modules.netserver.api.WebSocketReadHandler; |
| import org.netbeans.modules.web.webkit.debugging.spi.Command; |
| import org.netbeans.modules.web.webkit.debugging.spi.Response; |
| import org.openide.DialogDisplayer; |
| import org.openide.NotifyDescriptor; |
| import org.openide.util.Exceptions; |
| import org.openide.util.NbBundle; |
| |
| /** |
| * |
| * @author Jan Becicka |
| */ |
| public class AndroidDebugTransport extends MobileDebugTransport implements WebSocketReadHandler { |
| |
| private WebSocketClient webSocket; |
| private static final Logger LOGGER = Logger.getLogger(AndroidDebugTransport.class.getName()); |
| private boolean flush; |
| |
| @Override |
| public boolean detach() { |
| if (webSocket != null) { |
| webSocket.stop(); |
| } |
| return true; |
| } |
| |
| @Override |
| public void sendCommandImpl(Command command) { |
| String toString = translate(command.toString()); |
| webSocket.sendMessage(toString); |
| } |
| |
| @Override |
| public void accepted(SelectionKey key) { |
| synchronized(webSocket) { |
| webSocket.notifyAll(); |
| } |
| } |
| |
| @Override |
| public void read(SelectionKey key, byte[] message, Integer dataType) { |
| final String string; |
| string = new String(message, Charset.forName("UTF-8")).trim(); //NOI18N |
| try { |
| final Object parse = JSONValue.parseWithException(string); |
| if (callBack == null) { |
| LOGGER.info("callBack is null. Ignoring response: " + string); |
| } else { |
| callBack.handleResponse(new Response((JSONObject) parse)); |
| } |
| } catch (ParseException ex) { |
| Exceptions.attachMessage(ex, string); |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| |
| @Override |
| public void closed(SelectionKey key) { |
| WebKitDebuggingSupport.getDefault().stopDebugging(false); |
| } |
| |
| public String getConnectionName() { |
| return "Android"; //NOI18N |
| } |
| |
| public WebSocketClient createWebSocket(WebSocketReadHandler handler) throws IOException { |
| final URI uri = getURI(); |
| if (uri != null) { |
| return new WebSocketClient(uri, ProtocolDraft.getRFC(), handler); |
| } |
| return null; |
| } |
| |
| @Override |
| public boolean attach() { |
| try { |
| String s = ProcessUtilities.callProcess( |
| AndroidPlatform.getDefault().getAdbCommand(), |
| true, |
| AndroidPlatform.DEFAULT_TIMEOUT, |
| "forward", // NOI18N |
| "tcp:9222", getRedirectString()); //NOI18N |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| try { |
| webSocket = createWebSocket(this); |
| if (webSocket != null) { |
| webSocket.start(); |
| return true; |
| } |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| return false; |
| } |
| |
| private String getRedirectString() { |
| String appName = getBundleIdentifier(); |
| if (appName == null) { |
| //chrome |
| return "localabstract:chrome_devtools_remote"; |
| } else { |
| return "localabstract:webview_devtools_remote_" + AndroidPlatform.getDefault().getProcessIdByName(appName); |
| } |
| } |
| |
| |
| |
| @Override |
| public String getVersion() { |
| return "1.0"; //NOI18N |
| } |
| |
| @NbBundle.Messages({ |
| "LBL_UriTitle=Could not connect to device or emulator", |
| "ERR_CouldNotConnect=Please connect Android device (or run Android emulator) and make sure that:\n" |
| + "\u2022 No more than one device or emulator is connected at the same time\n" |
| + "\u2022 Device or emulator is listed in the output when invoking command \"adb devices\"\n" |
| + "\u2022 USB Debugging is enabled on your device (if applicable)\n" |
| + "\u2022 Your computer and Android device are connected to the same WiFi network (if applicable)" |
| }) |
| private URI getURI() { |
| JSONArray array = null; |
| for (long stop = System.nanoTime() + TimeUnit.MINUTES.toNanos(2); stop > System.nanoTime() && !flush;) { |
| try { |
| JSONParser parser = new JSONParser(); |
| |
| URL chromeJson = new URL("http://localhost:9222/json"); |
| try (BufferedReader reader = new BufferedReader(new InputStreamReader(chromeJson.openConnection(Proxy.NO_PROXY).getInputStream()))) { |
| Object obj = parser.parse(reader); |
| array = (JSONArray) obj; |
| if (array.size() == 0) { |
| try (BufferedReader r = new BufferedReader(new InputStreamReader(chromeJson.openConnection(Proxy.NO_PROXY).getInputStream()))) { |
| String line; |
| while ((line = r.readLine()) != null) { |
| LOGGER.info(line); |
| } |
| } |
| } |
| for (int i = 0; i < array.size(); i++) { |
| JSONObject object = (JSONObject) array.get(i); |
| String urlFromBrowser = object.get("url").toString(); // NOI18N |
| int hash = urlFromBrowser.indexOf("#"); // NOI18N |
| if (hash != -1) { |
| urlFromBrowser = urlFromBrowser.substring(0, hash); |
| } |
| if (urlFromBrowser.endsWith("/")) { // NOI18N |
| urlFromBrowser = urlFromBrowser.substring(0, urlFromBrowser.length() - 1); |
| } |
| URL conURL = getConnectionURL(); |
| if (conURL ==null) { |
| //phonegap |
| setBaseUrl(urlFromBrowser); |
| if (object.containsKey("webSocketDebuggerUrl")) { // NOI18N |
| return new URI(object.get("webSocketDebuggerUrl").toString()); // NOI18N |
| } else { |
| continue; |
| } |
| } |
| final String connectionUrl = conURL.toExternalForm(); |
| final String shortenedUrl = connectionUrl.replace(":80/", "/"); // NOI18N |
| |
| if (connectionUrl.equals(urlFromBrowser) || shortenedUrl.equals(urlFromBrowser)) { |
| if (object.containsKey("webSocketDebuggerUrl")) { // NOI18N |
| return new URI(object.get("webSocketDebuggerUrl").toString()); // NOI18N |
| } |
| } |
| } |
| } |
| } catch (IOException | ParseException | URISyntaxException ex) { |
| LOGGER.log(Level.FINE, "Cannot get websocket address, trying again...", ex); |
| try { |
| Thread.sleep(500); |
| } catch (InterruptedException ex1) { |
| Exceptions.printStackTrace(ex1); |
| } |
| } |
| } |
| if (array != null) { |
| LOGGER.info(array.toJSONString()); |
| } |
| |
| NotifyDescriptor not = new NotifyDescriptor( |
| Bundle.ERR_CouldNotConnect(), |
| Bundle.LBL_UriTitle(), |
| NotifyDescriptor.DEFAULT_OPTION, |
| NotifyDescriptor.WARNING_MESSAGE, |
| new Object[]{NotifyDescriptor.OK_OPTION}, |
| null); |
| DialogDisplayer.getDefault().notify(not); |
| LOGGER.info("Cannot get websocket address."); |
| return null; |
| } |
| |
| @Override |
| public void flush() { |
| flush=true; |
| } |
| } |