/*******************************************************************************
* Copyright (c) 2013, 2015 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat Inc. - initial API and implementation and/or initial documentation
* Kaloyan Raev <kaloyan.r@zend.com> - Bug 449766
*******************************************************************************/
package org.eclipse.thym.android.core.adt;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IStreamListener;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStreamMonitor;
import org.eclipse.thym.android.core.AndroidConstants;
import org.eclipse.thym.android.core.AndroidCore;
import org.eclipse.thym.core.HybridMobileStatus;
import org.eclipse.thym.core.HybridProjectConventions;
import org.eclipse.thym.core.internal.util.ExternalProcessUtility;
import org.eclipse.thym.core.internal.util.TextDetectingStreamListener;
/**
* Wrapper around the Android CommandLine tools.
*
* @author Gorkem Ercan
*
*/
public class AndroidSDKManager {
private String toolsDir;
private String platformTools;
private static class DeviceListParser implements IStreamListener{
private StringBuffer buffer = new StringBuffer();
@Override
public void streamAppended(String text, IStreamMonitor monitor) {
buffer.append(text);
}
public List<AndroidDevice> getDeviceList(){
if (buffer == null || buffer.length() < 1)
return null;
StringReader reader = new StringReader(buffer.toString());
BufferedReader read = new BufferedReader(reader);
String line =null;
ArrayList<AndroidDevice> list = new ArrayList<AndroidDevice>();
try{
while ((line = read.readLine()) != null) {
if(line.isEmpty() || line.contains("List of devices attached"))
continue;
String[] values = line.split("\t");
if(values.length == 2){
AndroidDevice device = new AndroidDevice();
device.setSerialNumber(values[0].trim());
device.setEmulator(values[0].contains("emulator"));
if("device".equals(values[1].trim())){
device.setState(AndroidDevice.STATE_DEVICE);
}
else if("offline".equals(values[1].trim())){
device.setState(AndroidDevice.STATE_OFFLINE);
}
list.add(device);
}
}
}
catch (IOException e) {
AndroidCore.log(IStatus.ERROR, "Error parsing the Android device list", e);
return null;
}
finally{
try{
read.close();
reader.close();
}catch(IOException e){/*ignored*/}
}
return list;
}
}
private static class AVDListParser implements IStreamListener{
private static final String PREFIX_NAME = "Name:";
private static final String MARKER_LEVEL = "API level";
private StringBuffer buffer = new StringBuffer();
@Override
public void streamAppended(String text, IStreamMonitor monitor) {
buffer.append(text);
}
public List<AndroidAVD> getAVDList(){
if (buffer == null || buffer.length() < 1)
return null;
StringReader reader = new StringReader(buffer.toString());
BufferedReader read = new BufferedReader(reader);
String line =null;
ArrayList<AndroidAVD> list = new ArrayList<AndroidAVD>();
try{
AndroidAVD currentAVD = null;
while ((line = read.readLine()) != null) {
int idx = line.indexOf(PREFIX_NAME);
if(idx > -1){
currentAVD = new AndroidAVD();
currentAVD.setName(line.substring(idx+PREFIX_NAME.length()).trim());
continue;
}
idx = line.indexOf(MARKER_LEVEL) ;
if(idx > -1 && currentAVD != null){
int startIndex = idx + MARKER_LEVEL.length();
int endIndex = line.lastIndexOf(')');
currentAVD.setApiLevel((line.substring(startIndex, endIndex).trim()));
list.add(currentAVD);
currentAVD = null;
}
}
}
catch (IOException e) {
AndroidCore.log(IStatus.ERROR, "Error parsing the AVD list", e);
return Collections.emptyList();
}
finally{
try{
read.close();
reader.close();
}catch(IOException e){/*ignored*/}
}
return list;
}
}
private static class TargetListParser implements IStreamListener{
private StringBuffer buffer = new StringBuffer();
@Override
public void streamAppended(String text, IStreamMonitor monitor) {
buffer.append(text);
}
public List<AndroidSDK> getSDKList() {
if (buffer == null || buffer.length() < 1)
return null;
StringReader reader = new StringReader(buffer.toString());
BufferedReader read = new BufferedReader(reader);
ArrayList<AndroidSDK> sdkList = new ArrayList<AndroidSDK>();
String line = null;
try {
AndroidSDK sdk = null;
while ((line = read.readLine()) != null) {
final int scolIdx = line.indexOf(':');
if (scolIdx < 0) {
continue;
}
String[] pair = new String[2];
pair[0] = line.substring(0, scolIdx).trim();
pair[1] = line.substring(scolIdx + 1).trim();
if ("id".equalsIgnoreCase(pair[0])) {
sdk = new AndroidSDK();
sdkList.add(sdk);
int vIndex = pair[1].indexOf("or");
sdk.setId(pair[1].substring(vIndex + "or".length())
.replace("\"", "").trim());
} else if ("Type".equalsIgnoreCase(pair[0])) {
Assert.isNotNull(sdk);
sdk.setType(pair[1].trim());
} else if ("API level".equalsIgnoreCase(pair[0])) {
Assert.isNotNull(sdk);
sdk.setApiLevel(pair[1]);
}
}
} catch (IOException e) {
AndroidCore.log(IStatus.ERROR, "Error parsing the SDK list", e);
}
finally{
try{
read.close();
reader.close();
}catch(IOException e){
//ignored
}
}
return sdkList;
}
}
private static class CreateProjectResultParser implements IStreamListener{
private StringBuffer buffer = new StringBuffer();
@Override
public void streamAppended(String text, IStreamMonitor monitor) {
buffer.append(text);
}
/**
* Returns an error string or null if it is OK
* @return
*/
public String getErrorString(){
String text = buffer.toString();
if (text.startsWith("Error:"))
{
StringReader reader = new StringReader(text);
BufferedReader read = new BufferedReader(reader);
try {
String line = read.readLine();
if(line==null){
return "";
}
return line.substring(7);
} catch (IOException e) {
AndroidCore.log(IStatus.ERROR, "Error parsing the create project command result", e);
}
finally{
try{
read.close();
reader.close();
}catch(IOException e){
//ignored
}
}
}
return null;
}
}
private AndroidSDKManager(String tools, String platform) {
toolsDir = tools;
platformTools = platform;
}
public static AndroidSDKManager getManager() throws CoreException{
String sdkDir = AndroidCore.getSDKLocation();
if(sdkDir == null ){
throw new CoreException(new HybridMobileStatus(IStatus.ERROR, AndroidCore.PLUGIN_ID, AndroidConstants.STATUS_CODE_ANDROID_SDK_NOT_DEFINED,
"Android SDK location is not defined", null));
}
Path path = new Path(sdkDir);
IPath tools = path.append("tools").addTrailingSeparator();
IPath platform = path.append("platform-tools").addTrailingSeparator();
AndroidSDKManager sdk = new AndroidSDKManager(tools.toOSString(), platform.toOSString());
return sdk;
}
public void createProject(AndroidSDK target, String projectName,
File path, String activity, String packageName, IProgressMonitor monitor) throws CoreException{
IStatus status = HybridProjectConventions.validateProjectName(projectName);
if(!status.isOK())
throw new CoreException(status);
//activity class name matches the project name
status = HybridProjectConventions.validateProjectName(activity);
if(!status.isOK())
throw new CoreException(status);
status = HybridProjectConventions.validateProjectID(packageName);
if(!status.isOK())
throw new CoreException(status);
ExternalProcessUtility processUtility = new ExternalProcessUtility();
StringBuilder command = new StringBuilder();
command.append(getAndroidCommand());
command.append(" create project");
command.append(" --target ").append(target.getId());
command.append(" --path ").append(addQuotes(path.getPath()));
command.append(" --name ").append(addQuotes(projectName));
command.append(" --activity ").append(activity);
command.append(" --package ").append(packageName);
CreateProjectResultParser parser = new CreateProjectResultParser();
processUtility.execSync(command.toString(), new File(toolsDir), parser, parser, monitor, null, null);
if( !monitor.isCanceled() && parser.getErrorString() != null ){
throw new CoreException(new Status(IStatus.ERROR,AndroidCore.PLUGIN_ID,"Error creating the Android project: "+ parser.getErrorString()));
}
}
public void updateProject(AndroidSDK sdk, String projectName, boolean isLibrary,File path, IProgressMonitor monitor)throws CoreException{
StringBuilder command = new StringBuilder(getAndroidCommand());
command.append(" update");
if(isLibrary){
command.append(" lib-project");
}else{
command.append(" project");
}
command.append( " --target ").append(sdk.getId());
if(projectName != null ){
IStatus status = HybridProjectConventions.validateProjectName(projectName);
if(!status.isOK()){
throw new CoreException(status);
}
command.append(" --name ").append('"').append(projectName).append('"');
}
command.append(" --path ").append('"').append(path.getPath()).append('"');
ExternalProcessUtility processUtility = new ExternalProcessUtility();
CreateProjectResultParser parser = new CreateProjectResultParser();
processUtility.execSync(command.toString(), new File(toolsDir), parser, parser, monitor, null, null);
if( !monitor.isCanceled() && parser.getErrorString() != null ){
throw new CoreException(new Status(IStatus.ERROR,AndroidCore.PLUGIN_ID,"Error creating the Android project: "+ parser.getErrorString()));
}
}
public void startADBServer() throws CoreException{
ExternalProcessUtility processUtility = new ExternalProcessUtility();
processUtility.execSync(getADBCommand()+" start-server",null, null, null, new NullProgressMonitor(), null, null);
}
public void killADBServer() throws CoreException{
ExternalProcessUtility processUtility = new ExternalProcessUtility();
processUtility.execSync(getADBCommand()+" kill-server",null, null, null, new NullProgressMonitor(), null, null);
}
public List<AndroidAVD> listAVDs() throws CoreException{
ExternalProcessUtility processUtility = new ExternalProcessUtility();
AVDListParser parser = new AVDListParser();
processUtility.execSync(getAndroidCommand()+" list avd", new File(toolsDir), parser, parser,
new NullProgressMonitor(), null, null);
return parser.getAVDList();
}
public List<AndroidSDK> listTargets() throws CoreException{
ExternalProcessUtility processUtility = new ExternalProcessUtility();
TargetListParser parser = new TargetListParser();
processUtility.execSync(getAndroidCommand()+" list target",
new File(toolsDir), parser, parser, new NullProgressMonitor(), null, null);
return parser.getSDKList();
}
public List<AndroidDevice> listDevices() throws CoreException{
ExternalProcessUtility processUtility = new ExternalProcessUtility();
DeviceListParser parser = new DeviceListParser();
processUtility.execSync(getADBCommand()+" devices", null, parser, parser,
new NullProgressMonitor(), null, null);
List<AndroidDevice> devices = parser.getDeviceList();
if(devices == null ){
devices = Collections.emptyList();
}
return devices;
}
public void installApk(File apkFile, String serialNumber, IProgressMonitor monitor) throws CoreException{
Assert.isNotNull(serialNumber);
ExternalProcessUtility processUtility = new ExternalProcessUtility();
StringBuilder command = new StringBuilder(getADBCommand());
command.append(" -s ").append(serialNumber);
command.append(" install");
command.append(" -r ");
command.append("\"").append(apkFile.getPath()).append("\"");
TextDetectingStreamListener listener = new TextDetectingStreamListener("Success");
processUtility.execSync(command.toString(), null,listener, listener, monitor, null, null);
if (!monitor.isCanceled() && !listener.isTextDetected()){
throw new CoreException(new Status(IStatus.ERROR, AndroidCore.PLUGIN_ID, "APK installation did not succeed"));
}
}
/**
* Wait for Android Emulator
* @param emulatorProcess android emulator process
* @param monitor progress monitor to be checked for cancellation
* @throws CoreException
*/
public void waitForEmulator(IProcess emulatorProcess, IProgressMonitor monitor) throws CoreException{
while(!emulatorProcess.isTerminated()){ //check if process is terminated - could not start etc..
List<AndroidDevice> devices = this.listDevices();
if(devices != null ){
for (AndroidDevice androidDevice : devices) {
if(androidDevice.isEmulator() && androidDevice.getState() == AndroidDevice.STATE_DEVICE)
return;
}
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new CoreException(new Status(IStatus.ERROR, AndroidCore.PLUGIN_ID, "Exception occured while waiting for emulator",e));
}
if (monitor.isCanceled()){
throw new CoreException(new Status(IStatus.ERROR, AndroidCore.PLUGIN_ID, "Operation cancelled"));
}
}
throw new CoreException(new Status(IStatus.ERROR, AndroidCore.PLUGIN_ID, "Android emulator was terminated"));
}
public void startApp(String component, String serialNumber, IProgressMonitor monitor) throws CoreException{
ExternalProcessUtility processUtility = new ExternalProcessUtility();
StringBuilder command = new StringBuilder(getADBCommand());
command.append(" -s ").append(serialNumber);
command.append(" shell am start");
command.append(" -n ");
command.append(component);
processUtility.execSync(command.toString(), null, null, null, monitor, null, null);
}
public void logcat(String filter, IStreamListener outListener, IStreamListener errorListener, String serialNumber) throws CoreException{
ExternalProcessUtility processUtility = new ExternalProcessUtility();
StringBuilder command = new StringBuilder(getADBCommand());
command.append(" -s ").append(serialNumber);
command.append(" logcat ");
if(filter !=null && !filter.isEmpty()){
command.append(filter);
}
processUtility.execAsync(command.toString(), null, outListener, errorListener, null);
}
public IProcess startEmulator(String avd) throws CoreException{
ExternalProcessUtility processUtility = new ExternalProcessUtility();
StringBuilder command = new StringBuilder(getEmulatorCommand());
command.append(" -no-boot-anim");
command.append(" -avd ").append(avd);
return processUtility.exec(DebugPlugin.parseArguments(command.toString()), null, null, null, null, null, null);
}
public void startAVDManager() throws CoreException{
ExternalProcessUtility processUtility = new ExternalProcessUtility();
StringBuilder command = new StringBuilder(getAndroidCommand());
command.append(" avd ");
processUtility.execAsync(command.toString(), new File(toolsDir), null, null, null);
}
private String getAndroidCommand(){
String scriptName = isWindows() ? "android.bat" : "android";
return addQuotes(toolsDir + scriptName);
}
private String getADBCommand(){
return addQuotes(platformTools+"adb");
}
private String getEmulatorCommand(){
return addQuotes(toolsDir+"emulator");
}
private boolean isWindows(){
String OS = System.getProperty("os.name","unknown");
return OS.toLowerCase().indexOf("win")>-1;
}
private String addQuotes(String path) {
return "\"" + path + "\"";
}
}