/*
* Software Name : ATK
*
* Copyright (C) 2007 - 2012 France Télécom
*
* Licensed 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.
*
* ------------------------------------------------------------------
* File Name : AndroidWizard.java
*
* Created : 13/08/2010
* Author(s) : HENAFF Mari-Mai
*/
package com.orange.atk.phone.android.wizard;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Toolkit;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.IShellOutputReceiver;
import com.android.ddmlib.InstallException;
import com.android.ddmlib.ShellCommandUnresponsiveException;
import com.android.ddmlib.TimeoutException;
import com.orange.atk.error.ErrorManager;
import com.orange.atk.internationalization.ResourceManager;
import com.orange.atk.phone.PhoneException;
import com.orange.atk.phone.android.AndroidDriver;
import com.orange.atk.phone.detection.AutomaticPhoneDetection;
import com.orange.atk.platform.Platform;
import com.orange.atk.util.Position;
public class AndroidWizard extends JFrame {
private static final String TITLE= "Android Wizard";
protected final static int PORT_ATK_WIZARD = 1358;
private String configFileName = "";
private Hashtable<Integer,JPanel> wizardSteps = new Hashtable<Integer,JPanel>();
private Hashtable<Integer,String> wizardStepTitle = new Hashtable<Integer,String>();
private Hashtable<String,String> detectedChannels = new Hashtable<String,String>();
private Hashtable<String,StringBuffer> channelEvents = new Hashtable<String,StringBuffer>();
AndroidDriver phone;
IDevice device;
int currentStep = -1;
// Android Config file parameters
private String touchscreen = "";
private String keyboard = "";
private String keyboard2 = "";
private String keyboard3 = "";
private HashMap<String, Position> softKeyMap = new HashMap<String, Position>(); //HashMap<keyName,(X,Y)>
private HashMap<String, Integer> keyMap = new HashMap<String, Integer>(); //HashMap<keyName,keyCode>
private HashMap<String, String> keyCanal = new HashMap<String, String>(); // HashMap<keyName,keyboard>
private HashMap<String, String> softKeyCanal = new HashMap<String, String>(); // HashMap<keyName,keyboard>
private boolean ATKWizardInstalled = false;
private Socket socket;
private int screenWidth = 0;
private int screenHeight = 0;
private int codeX = -1;
private int maxX = 0;
private int codeY = -1;
private int maxY = 0;
private String patternX;
private String patternY;
private IShellOutputReceiver shellOutputReceiver = new IShellOutputReceiver() {
public void addOutput(byte[] data, int offset, int length) {}
public void flush() {}
public boolean isCancelled() {
return false;
}
};
public AndroidWizard(AndroidDriver phone, IDevice device, String confFileName) throws PhoneException {
super(TITLE);
configFileName = confFileName;
this.device = device;
this.phone = phone;
try {
device.executeShellCommand("getevent -p",
new DetectAllChannelsEventFilter(detectedChannels, channelEvents));
} catch (IOException e) {
String error = ResourceManager.getInstance().getString("ANDROID_CHANNEL_DETECTION_ERROR");
ErrorManager.getInstance().addWarning(getClass().getName(), error, e);
throw new PhoneException(error);
} catch (TimeoutException e) {
String error = ResourceManager.getInstance().getString("ANDROID_CHANNEL_DETECTION_ERROR");
ErrorManager.getInstance().addWarning(getClass().getName(), error, e);
throw new PhoneException(error);
} catch (AdbCommandRejectedException e) {
String error = ResourceManager.getInstance().getString("ANDROID_CHANNEL_DETECTION_ERROR");
ErrorManager.getInstance().addWarning(getClass().getName(), error, e);
throw new PhoneException(error);
} catch (ShellCommandUnresponsiveException e) {
String error = ResourceManager.getInstance().getString("ANDROID_CHANNEL_DETECTION_ERROR");
ErrorManager.getInstance().addWarning(getClass().getName(), error, e);
throw new PhoneException(error);
}
addStep(new StartWizardPanel(this,phone),"Android Wizard");
addStep(new TouchscreenChannelPanel(this,device,detectedChannels),"Select the channel for the touchscreen");
this.setSize(600, 400);
getContentPane().setLayout(new FlowLayout());
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
this.addWindowListener(new WindowListener(){
public void windowActivated(WindowEvent arg0) {}
public void windowClosed(WindowEvent arg0) {}
public void windowClosing(WindowEvent arg0) {
exit(false);
}
public void windowDeactivated(WindowEvent arg0) {}
public void windowDeiconified(WindowEvent arg0) {}
public void windowIconified(WindowEvent arg0) {}
public void windowOpened(WindowEvent arg0) {}
});
Dimension sSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension fSize = this.getSize();
this.setLocation((sSize.width-fSize.width)/2, (sSize.height-fSize.height)/2);
goToStep(0);
}
public void addStep(JPanel panel,String title) {
wizardSteps.put(new Integer(currentStep+1),panel);
wizardStepTitle.put(new Integer(currentStep+1), title);
goToStep(currentStep+1);
}
public void exit(boolean force) {
if (force) close();
else {
int result = JOptionPane.showConfirmDialog(this,
"Are you sure you want to exit wizard ? Configuration file won't be generated.",
"Confirmation",JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_OPTION) {
close();
}
}
}
private void close() {
this.dispose();
try {
this.uninstallATKWizard();
} catch (PhoneException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void nextStep() {
goToStep(currentStep+1);
}
public void prevStep() {
goToStep(currentStep-1);
}
private void goToStep(int step) {
currentStep=step;
getContentPane().removeAll();
if (step>0) this.setTitle(TITLE+": "+wizardStepTitle.get(new Integer(step))+" (Step "+step+")");
if (wizardSteps.get(new Integer(step)) == null) Logger.getLogger(this.getClass()).debug("Step "+step+" is null !!");
getContentPane().add(wizardSteps.get(new Integer(step)));
pack();
setVisible(true);
}
protected void installATKWizard() throws PhoneException {
if (!ATKWizardInstalled) {
Logger.getLogger(this.getClass()).debug("Installing ATK Wizard on phone");
//push ATKMonitor to the Device
try {
String result = device.uninstallPackage("com.orange.atk.wizard");
if(result!=null){
Logger.getLogger(this.getClass()).debug("Result of the uninstall: "+result);
}
result = device.installPackage(
Platform.getInstance().getJATKPath()+Platform.FILE_SEPARATOR+"AndroidTools"+Platform.FILE_SEPARATOR+"ATKWizard.apk", true);
if(result!=null){
Logger.getLogger(this.getClass()).debug("Result of the push: "+result);
}
} catch (InstallException e) {
e.printStackTrace();
throw new PhoneException("ATK Wizard - unable to install ATK Wizard");
}
//Forward tcp port
String adbLocation = Platform.getInstance().getDefaultADBLocation();
Runtime r =Runtime.getRuntime();
String [] args1 = {adbLocation, "forward" ,"tcp:"+PORT_ATK_WIZARD,"tcp:"+PORT_ATK_WIZARD};
Process p;
BufferedReader in_br=null;
try {
p = r.exec(args1);
in_br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line ="";
while ((line =in_br.readLine()) != null) {
Logger.getLogger(this.getClass() ).debug("adb forward done : "+line);
if (line.contains("daemon")) {throw new PhoneException("ATK Wizard - unable to install ATK Wizard");}
}
in_br.close();
} catch (Exception e) {
e.printStackTrace();
throw new PhoneException("ATK Wizard - unable to install ATK Wizard");
}
Logger.getLogger(this.getClass() ).debug("adb forward done for port "+PORT_ATK_WIZARD+"\n");
ATKWizardInstalled = true;
}
}
protected void startATKWizard() throws PhoneException {
Logger.getLogger(this.getClass()).debug("Starting ATK Wizard");
//run ATKWizard
try {
device.executeShellCommand("am start -n com.orange.atk.wizard/.ATKWizardClient",shellOutputReceiver);
} catch (IOException e) {
e.printStackTrace();
throw new PhoneException("ATK Wizard - unable to launch ATK Wizard");
} catch (TimeoutException e) {
e.printStackTrace();
throw new PhoneException("ATK Wizard - unable to launch ATK Wizard");
} catch (AdbCommandRejectedException e) {
e.printStackTrace();
throw new PhoneException("ATK Wizard - unable to launch ATK Wizard");
} catch (ShellCommandUnresponsiveException e) {
e.printStackTrace();
throw new PhoneException("ATK Wizard - unable to launch ATK Wizard");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e2) {}
Logger.getLogger(this.getClass()).debug("ATK Wizard is launched on the device ...");
}
protected void uninstallATKWizard() throws PhoneException {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
throw new PhoneException("ATK Wizard - unable to uninstall ATK Wizard");
}
}
Logger.getLogger(this.getClass()).debug("Uninstalling ATK Wizard from phone");
try {
String result = device.uninstallPackage("com.orange.atk.wizard");
if(result!=null){
Logger.getLogger(this.getClass()).debug("Result of the uninstall: "+result);
}
} catch (InstallException e) {
e.printStackTrace();
throw new PhoneException("ATK Wizard - unable to uninstall ATK Wizard");
}
}
protected Socket getWizardSocket() throws UnknownHostException, IOException {
if (socket==null) socket = new Socket("127.0.0.1", PORT_ATK_WIZARD);
return socket;
}
public void printReport() {
String configXmlFileName = configFileName+".xml";
Document configxml = DocumentHelper.createDocument();
configxml.addComment(" "+phone.getName()+" configuration file ");
configxml.addComment(" Screen resolution "+screenWidth+"x"+screenHeight+" ");
Element root = configxml.addElement("Android-config");
Element canalPattern = root.addElement("CanalPattern");
//keyboards
if(!keyboard.equals("")) printKeyMapping("keyboard", keyboard, canalPattern, root);
if(!keyboard2.equals("")) printKeyMapping("keyboard2", keyboard2, canalPattern, root);
if(!keyboard3.equals("")) printKeyMapping("keyboard3", keyboard3, canalPattern, root);
Set<String> softKeySet = softKeyMap.keySet();
Element keyMapping = root.addElement("SoftKeyMapping");
for(String key : softKeySet){
keyMapping.addElement("Key")
.addAttribute( "name", key)
.addAttribute( "avgX", ""+softKeyMap.get(key).getX())
.addAttribute( "avgY", ""+softKeyMap.get(key).getY());
}
//touchscreen
if(!touchscreen.equals("")){
canalPattern.addElement("Pattern")
.addAttribute( "canal", "touchscreen")
.addAttribute( "value", touchscreen.replace("\"",""));
Element Touchscreen = root.addElement("Touchscreen");
Touchscreen.addComment("!!! THIS IS JUST TOUCHSCREEN Elements TEMPLATE !!!");
Touchscreen.addComment("!!! SEE ATK User Guide - Configuration section - and UPDATE following values !!!");
if (codeX!=-1 && codeY!=-1) {
Touchscreen.addComment("!!! Please check following X Y ratioX and ratioY patterns");
Touchscreen.addElement("Pattern")
.addAttribute( "name", "X")
.addAttribute( "value", "3 "+codeX+" ");
Touchscreen.addElement("Pattern")
.addAttribute( "name", "Y")
.addAttribute( "value", "3 "+codeY+" ");
long ratio = maxX*100/screenWidth;
Touchscreen.addElement("Pattern")
.addAttribute( "name", "ratioX")
.addAttribute( "value", String.valueOf( (double)ratio / 100.0 ));
ratio = maxY*100/screenHeight;
Touchscreen.addElement("Pattern")
.addAttribute( "name", "ratioY")
.addAttribute( "value", String.valueOf( (double)ratio / 100.0 ));
Touchscreen.addComment("!!! Please update following patterns");
} else {
Touchscreen.addElement("Pattern")
.addAttribute( "name", "X")
.addAttribute( "value", "0 0 ");
Touchscreen.addElement("Pattern")
.addAttribute( "name", "Y")
.addAttribute( "value", "0 0 ");
Touchscreen.addElement("Pattern")
.addAttribute( "name", "ratioX")
.addAttribute( "value", "1.0");
Touchscreen.addElement("Pattern")
.addAttribute( "name", "ratioY")
.addAttribute( "value", "1.0");
}
Touchscreen.addElement("Pattern")
.addAttribute( "name", "down")
.addAttribute( "value", "0 0 0");
Touchscreen.addElement("Pattern")
.addAttribute( "name", "downmax")
.addAttribute( "value", "0 0 0");
Touchscreen.addElement("Pattern")
.addAttribute( "name", "up")
.addAttribute( "value", "0 0 0");
Touchscreen.addElement("Pattern")
.addAttribute( "name", "flush")
.addAttribute( "value", "0 0 0");
Touchscreen.addElement("Pattern")
.addAttribute( "name", "flush2")
.addAttribute( "value", "0 2 0");
Touchscreen.addElement("Threshold")
.addAttribute( "name", "move")
.addAttribute( "value", "15");
Touchscreen.addElement("Option")
.addAttribute( "name", "sendMouseDownForMove")
.addAttribute( "value", "true");
Touchscreen.addElement("Option")
.addAttribute( "name", "sendMouseEventFirst")
.addAttribute( "value", "true");
Touchscreen.addElement("Option")
.addAttribute( "name", "useMonkeyForPress")
.addAttribute( "value", "true");
}
//Write the file.
OutputFormat format = OutputFormat.createPrettyPrint();
XMLWriter writer =null;
try {
writer = new XMLWriter(new FileWriter(configXmlFileName),format);
writer.write(configxml);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
//Show a confirm dialog and exit the wizard
JOptionPane.showConfirmDialog(this,
"A template of the config file has been created under \n"+
configXmlFileName+"\n"+
"See ATK User guide to configure the Touchscreen section of this template\n",
"Success",JOptionPane.CLOSED_OPTION);
exit(true);
}
private void printKeyMapping(String canal, String value, Element canalPattern, Element root) {
Set<String> keySet = keyCanal.keySet();
canalPattern.addElement("Pattern")
.addAttribute( "canal", canal)
.addAttribute( "value", value.replace("\"",""));
Element keyMapping = root.addElement("KeyMapping").addAttribute( "canal", canal);
for(String key : keySet){
if(keyCanal.get(key).equals(canal)){
keyMapping.addElement("Key")
.addAttribute( "name", key)
.addAttribute( "code", ""+keyMap.get(key));
}
}
}
public void setScreenWidth(int screenWidth) {
this.screenWidth = screenWidth;
}
public void setScreenHeight(int screenHeight) {
this.screenHeight = screenHeight;
}
public String getPatternX() {
return patternX;
}
public String getPatternY() {
return patternY;
}
public double getRatioX() {
if (screenWidth!=0) return ((double)maxX/(double)screenWidth);
return 0;
}
public double getRatioY() {
if (screenHeight!=0) return ((double)maxY/(double)screenHeight);
return 0;
}
/******** Config file parameters *********/
public String getKeyboard(int keyboardNb) {
if (keyboardNb==1)
return keyboard;
else if (keyboardNb==2)
return keyboard2;
else if (keyboardNb==3)
return keyboard3;
return keyboard;
}
public void setKeyboard(int keyboardNb, String keyboard) {
if (keyboardNb==1)
this.keyboard = keyboard;
else if (keyboardNb==2)
this.keyboard2 = keyboard;
else if (keyboardNb==3)
this.keyboard3 = keyboard;
}
public void registerKey(int keyboard ,String keyName, int keyCode) {
String keyboardType = "keyboard";
if (keyboard==2) keyboardType = "keyboard2";
else if (keyboard==3) keyboardType = "keyboard3";
keyCanal.put(keyName,keyboardType);
keyMap.put(keyName, new Integer(keyCode));
}
public void registerSoftKey(int keyboard ,String keyName, int avgX, int avgY) {
// this is soft keyboard == always touchscreen
if (keyboard==1) this.keyboard = "";
else if (keyboard==2) this.keyboard2 = "";
else if (keyboard==3) this.keyboard3 = "";
softKeyMap.put(keyName, new Position(avgX, avgY, 0));
}
public void setTouchscreen(String touchscreen, StringBuffer traces){
this.touchscreen = touchscreen;
// Analyze traces to get X Y event codes
String[] tracesEvents = traces.toString().split("\n");
int traceX = -1;
int traceY = -1;
for (int i=0; i<tracesEvents.length && i<20 && traceY==-1; i++) {
String traceEvent = tracesEvents[i];
Logger.getLogger(this.getClass()).debug("###"+traceEvent);
Matcher mtc = Pattern.compile("\\w*-\\w*:\\s*(\\w*)\\s*(\\w*)\\s*(\\w*)\\s*").matcher(traceEvent);
if (mtc.matches()) {
int code = Integer.parseInt(mtc.group(2), 16);
Logger.getLogger(this.getClass()).debug("TRACE CODE="+code);
if (traceX==-1) {
traceX = code;
} else {
if (code == traceX+1) {
traceY = code;
Logger.getLogger(this.getClass()).debug("traceX="+traceX+" traceY="+traceY);
} else traceX=code;
}
}
}
// Analyze touchscreen events to get X Y ratio templates
String touchEventsDescription = channelEvents.get(touchscreen).toString();
String[] touchEvents = touchEventsDescription.split("\n");
for (int i=0; i<touchEvents.length && codeY==-1; i++) {
String touchEvent = touchEvents[i];
if (touchEvent.indexOf(":")!=-1) touchEvent = touchEvent.substring(touchEvent.indexOf(":")+1);
Logger.getLogger(this.getClass()).debug("***"+touchEvent);
Matcher mtc = Pattern.compile("\\s*(\\d*)\\s*value\\s*(-?\\d+),\\s*min\\s*(-?\\d+),\\s*max\\s*(-?\\d+),\\s*fuzz\\s*(-?\\d+)\\s*flat\\s*(-?\\d+)\\s*").matcher(touchEvent);
if (mtc.matches()) {
int code = Integer.parseInt(mtc.group(1), 16);
Logger.getLogger(this.getClass()).debug("CODE="+code);
if (code == codeX+1 && code>0 && (traceY==-1 || traceY==code)) {
codeY = code;
patternY = "0003 "+mtc.group(1);
Logger.getLogger(this.getClass()).debug("codeX="+codeX+" codeY="+codeY);
maxY = Integer.parseInt(mtc.group(4));
} else {
codeX = code;
patternX = "0003 "+mtc.group(1);
maxX = Integer.parseInt(mtc.group(4));
}
}
}
if (codeY==-1) codeX=-1;
}
}