blob: cb5345c75499c6a52d9f07c4fea4009c90b18e14 [file] [log] [blame]
/*
* 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 transient final 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()));
}
}