| /* |
| * 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 org.netbeans.modules.cordova.platforms.spi.MobilePlatform; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.StringReader; |
| import java.nio.file.Files; |
| import java.nio.file.Paths; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import javax.swing.SwingUtilities; |
| import org.apache.tools.ant.module.api.support.ActionUtils; |
| import org.netbeans.modules.cordova.platforms.spi.Device; |
| import org.netbeans.modules.cordova.platforms.api.PlatformManager; |
| import org.netbeans.modules.cordova.platforms.api.ProcessUtilities; |
| import org.netbeans.modules.cordova.platforms.spi.ProvisioningProfile; |
| import org.netbeans.modules.cordova.platforms.spi.SDK; |
| import org.openide.execution.ExecutorTask; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.util.EditableProperties; |
| import org.openide.util.Exceptions; |
| import org.openide.util.NbPreferences; |
| import org.openide.util.RequestProcessor; |
| import org.openide.util.Utilities; |
| import org.openide.util.lookup.ServiceProvider; |
| |
| /** |
| * |
| * @author Jan Becicka |
| */ |
| @ServiceProvider(service=MobilePlatform.class) |
| public class AndroidPlatform implements MobilePlatform { |
| |
| private static String ANDROID_SDK_ROOT_PREF = "android.sdk.home"; //NOI18N |
| |
| public static int DEFAULT_TIMEOUT = 30000; |
| |
| private final transient java.beans.PropertyChangeSupport propertyChangeSupport = new java.beans.PropertyChangeSupport(this); |
| |
| public AndroidPlatform() { |
| } |
| |
| public static AndroidPlatform getDefault() { |
| return (AndroidPlatform) PlatformManager.getPlatform(PlatformManager.ANDROID_TYPE); |
| } |
| |
| // public void createProject(File dir, String targetId, String projectName, String activityName, String packageName) throws IOException { |
| // ProcessBuilder pb = ProcessBuilder.getLocal(); |
| // pb.setExecutable(getSdkLocation() + "/tools/android"); |
| // pb.setArguments( |
| // Arrays.asList( |
| // "create", "project", |
| // "--target", targetId, |
| // "--name", projectName, |
| // "--path", dir.getPath(), |
| // "--activity", activityName, |
| // "--package", packageName |
| // )); |
| // pb.setWorkingDirectory(dir.getParentFile().getAbsolutePath()); |
| // try { |
| // Process call = pb.call(); |
| // call.waitFor(); |
| // InputStreamReader inputStreamReader = new InputStreamReader(new BufferedInputStream(call.getErrorStream())); |
| // if (call.exitValue() != 0) { |
| // StringBuilder error = new StringBuilder(); |
| // char[] ch = new char[1]; |
| // while (inputStreamReader.ready()) { |
| // inputStreamReader.read(ch); |
| // error.append(ch); |
| // } |
| // throw new IOException(error.toString()); |
| // } |
| // } catch (InterruptedException ex) { |
| // throw new IOException(ex); |
| // } |
| // } |
| |
| @Override |
| public Collection<Device> getVirtualDevices() throws IOException { |
| assert !SwingUtilities.isEventDispatchThread(); |
| String avdString = ProcessUtilities.callProcess(getAndroidCommand(), true, AndroidPlatform.DEFAULT_TIMEOUT, "list", "avd"); //NOI18N |
| return AVD.parse(avdString); |
| } |
| |
| private String getAndroidCommand() { |
| if (Utilities.isWindows()) { |
| return getSdkLocation() + "\\tools\\android.bat"; // NOI18N |
| } else { |
| return getSdkLocation() + "/tools/android"; // NOI18N |
| } |
| } |
| |
| String getAdbCommand() { |
| if (Utilities.isWindows()) { |
| return getSdkLocation() + "\\platform-tools\\adb.exe"; // NOI18N |
| } else { |
| return getSdkLocation() + "/platform-tools/adb"; // NOI18N |
| |
| } |
| } |
| |
| |
| @Override |
| public Collection<SDK> getSDKs() throws IOException { |
| //assert !SwingUtilities.isEventDispatchThread(); |
| String avdString = ProcessUtilities.callProcess(getAndroidCommand(), true, 30000, "list", "target");//NOI18N |
| return Target.parse(avdString); |
| } |
| |
| private final HashSet<String> targets = new HashSet<String>(Arrays.asList(new String[]{ |
| "android-14", //NOI18N |
| "android-15", //NOI18N |
| "android-16", //NOI18N |
| "android-17", //NOI18N |
| "android-18", //NOI18N |
| "android-19", //NOI18N |
| "android-20", //NOI18N |
| "android-21", //NOI18N |
| "android-22"})); //NOI18N |
| |
| |
| @Override |
| public SDK getPrefferedTarget() { |
| try { |
| final Collection<SDK> targets1 = getSDKs(); |
| for (SDK t: targets1) { |
| if (targets.contains(t.getName())) { |
| return t; |
| } |
| } |
| if (targets1.iterator().hasNext()) { |
| return targets1.iterator().next(); |
| } |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| return null; |
| } |
| |
| |
| @Override |
| public Collection<org.netbeans.modules.cordova.platforms.spi.Device> getConnectedDevices() throws IOException { |
| //assert !SwingUtilities.isEventDispatchThread(); |
| String avdString = ProcessUtilities.callProcess(getAdbCommand(), true, AndroidPlatform.DEFAULT_TIMEOUT, "devices"); //NOI18N |
| Collection<org.netbeans.modules.cordova.platforms.spi.Device> devices = AndroidDevice.parse(avdString); |
| if (devices.isEmpty()) { |
| //maybe adb is just down. try to restart adb |
| try { |
| ProcessUtilities.callProcess(getAdbCommand(), true, AndroidPlatform.DEFAULT_TIMEOUT, "kill-server"); //NOI18N |
| ProcessUtilities.callProcess(getAdbCommand(), true, AndroidPlatform.DEFAULT_TIMEOUT, "start-server"); //NOI18N |
| } catch (IOException ioe) { |
| //ignore |
| } |
| } |
| avdString = ProcessUtilities.callProcess(getAdbCommand(), true, AndroidPlatform.DEFAULT_TIMEOUT, "devices"); //NOI18N |
| devices = AndroidDevice.parse(avdString); |
| return devices; |
| } |
| |
| |
| public ExecutorTask buildProject(File dir, String... targets) throws IOException { |
| File build = new File(dir.getAbsolutePath() + File.separator + "build.xml"); //NOI18N |
| FileObject buildFo = FileUtil.toFileObject(build); |
| return ActionUtils.runTarget(buildFo, targets, null); |
| } |
| |
| /** |
| * Deletes dir and all subdirectories/files! |
| * @param dir |
| * @throws IOException |
| */ |
| public void cleanProject(File dir) throws IOException { |
| FileUtil.toFileObject(dir).delete(); |
| } |
| |
| @Override |
| public String getSdkLocation() { |
| String sdkLocation = NbPreferences.forModule(AndroidPlatform.class).get(ANDROID_SDK_ROOT_PREF, null); |
| if (sdkLocation != null && !sdkLocation.isEmpty()) { |
| return sdkLocation; |
| } else { |
| return getSdkFromAndroidHome(); |
| } |
| } |
| |
| @Override |
| public void setSdkLocation(String sdkLocation) { |
| NbPreferences.forModule(AndroidPlatform.class).put(ANDROID_SDK_ROOT_PREF, sdkLocation); |
| propertyChangeSupport.firePropertyChange("SDK", null, sdkLocation);//NOI18N |
| } |
| |
| @Override |
| public boolean waitEmulatorReady(int timeout) { |
| try { |
| return RequestProcessor.getDefault().invokeAny(Collections.singleton(new Callable<Boolean>() { |
| |
| @Override |
| public Boolean call() throws Exception { |
| return waitEmulatorReady(); |
| } |
| }), timeout, TimeUnit.SECONDS); |
| } catch (InterruptedException ex) { |
| } catch (ExecutionException ex) { |
| } catch (TimeoutException ex) { |
| } |
| return false; |
| |
| } |
| |
| private boolean waitEmulatorReady() { |
| try { |
| String value; |
| for(;;) { |
| value = ProcessUtilities.callProcess( |
| getAdbCommand(), |
| true, |
| -1, |
| "-e", // NOI18N |
| "wait-for-device", // NOI18N |
| "shell", // NOI18N |
| "getprop", // NOI18N |
| "init.svc.bootanim"); //NOI18N |
| if ("stopped".equals(value.trim())) { //NOI18N |
| return true; |
| } |
| try { |
| Thread.sleep(1000); |
| } catch (InterruptedException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| return false; |
| |
| } |
| |
| @Override |
| public void manageDevices() { |
| assert !SwingUtilities.isEventDispatchThread(); |
| try { |
| ProcessUtilities.callProcess(getAndroidCommand(), true, -1, "avd"); //NOI18N |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| |
| @Override |
| public boolean isReady() { |
| return getSdkLocation() != null && !getSdkLocation().isEmpty(); |
| } |
| |
| /** |
| * Add PropertyChangeListener. |
| * |
| * @param listener |
| */ |
| @Override |
| public void addPropertyChangeListener(java.beans.PropertyChangeListener listener ) { |
| propertyChangeSupport.addPropertyChangeListener( listener ); |
| } |
| |
| /** |
| * Remove PropertyChangeListener. |
| * |
| * @param listener |
| */ |
| @Override |
| public void removePropertyChangeListener(java.beans.PropertyChangeListener listener ) { |
| propertyChangeSupport.removePropertyChangeListener( listener ); |
| } |
| |
| |
| @Override |
| public String getType() { |
| return PlatformManager.ANDROID_TYPE; |
| } |
| |
| @Override |
| public String getSimulatorPath() { |
| throw new UnsupportedOperationException("Not supported yet."); // NOI18N |
| } |
| |
| @Override |
| public Device getDevice(String name, EditableProperties props) { |
| return AndroidDevice.get(name, props); |
| } |
| |
| @Override |
| public String getCodeSignIdentity() { |
| throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. // NOI18N |
| } |
| |
| @Override |
| public String getProvisioningProfilePath() { |
| throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. // NOI18N |
| } |
| |
| @Override |
| public void setCodeSignIdentity(String identity) { |
| throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. // NOI18N |
| } |
| |
| @Override |
| public void setProvisioningProfilePath(String path) { |
| throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. // NOI18N |
| } |
| |
| @Override |
| public Collection<? extends ProvisioningProfile> getProvisioningProfiles() { |
| return Collections.emptyList(); |
| } |
| |
| public String getProcessIdByName(String appName) { |
| try { |
| String result = ProcessUtilities.callProcess(getAdbCommand(), true, AndroidPlatform.DEFAULT_TIMEOUT, "shell", "ps"); //NOI18N |
| BufferedReader r = new BufferedReader(new StringReader(result)); |
| String line; |
| while ((line = r.readLine()) != null) { |
| if (line.trim().endsWith(appName)) { |
| Pattern column = Pattern.compile("(\\S+)(\\s+)(\\S+)(\\s+)(.+)"); |
| Matcher matcher = column.matcher(line); |
| if (matcher.matches()) { |
| return matcher.group(3); |
| } |
| } |
| } |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| return null; |
| } |
| |
| boolean isWebViewDebugSupported(boolean emulator) { |
| try { |
| String version = ProcessUtilities.callProcess( |
| getAdbCommand(), |
| true, |
| -1, |
| emulator?"-e":"-d", // NOI18N |
| "wait-for-device", // NOI18N |
| "shell", // NOI18N |
| "getprop", //NOI18N |
| "ro.build.version.release"); //NOI18N |
| |
| return version.compareTo("4.4") >= 0; |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| return false; |
| } |
| |
| |
| } |
| |
| /** |
| * Sets the Android SDK location from ANDROID_HOME environment variable, if |
| * available. |
| * |
| * @return Android SDK Location or <code>null</code> if Android SDK could |
| * not be found. |
| */ |
| private String getSdkFromAndroidHome() { |
| String androidHomeValue = System.getenv("ANDROID_HOME"); //NOI18N |
| if (androidHomeValue != null && !androidHomeValue.isEmpty()) { |
| File androidLoc = new File(androidHomeValue); |
| File androidTools = new File(androidLoc, "platform-tools"); //NOI18N |
| boolean validSdk = androidLoc.exists() && androidLoc.isDirectory() |
| && androidTools.exists() && androidTools.isDirectory(); |
| if (validSdk) { |
| NbPreferences.forModule(AndroidPlatform.class).put(ANDROID_SDK_ROOT_PREF, androidHomeValue); |
| return androidHomeValue; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Performs a check whether adb executable is available |
| * |
| * @return <code>true</code> if adb command has been found, |
| * <code>false</code> otherwise |
| */ |
| boolean adbCommandExists() { |
| return Files.exists(Paths.get(getAdbCommand())); |
| } |
| } |