/*
* 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 : AndroidDriver.java
*
* Created : 07/12/2009
* Author(s) : Yvain Leyral
*/
package com.orange.atk.phone.android;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.log4j.Logger;
import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.IDevice;
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.PhoneInterface;
import com.orange.atk.phone.android.wizard.AndroidWizard;
import com.orange.atk.platform.Platform;
import com.orange.atk.util.Position;
/**
* This class is the base for model specific driver.
* @author Leral Yvain
*
*/
public class AndroidDriver extends AndroidPhone implements PhoneInterface {
protected String EVENT_KEY = "";
protected String EVENT_KEY2 = "";
protected String EVENT_KEY3 = "";
protected float SCREEN_RATIO_X = 1;
protected float SCREEN_RATIO_Y = 1;
protected String EVENT_MOUSE_UP = "";
protected String EVENT_MOUSE_DOWN = "";
protected String EVENT_MOUSE_MOVE = "";
protected String EVENT_X="";
protected String EVENT_Y="";
protected String EVENT_XY="";
protected String EVENT_FLUSH2="";
protected String EVENT_FLUSH="";
protected String MOUSE_CHANNEL_EVENT="";
protected String KEY_CHANNEL_EVENT="";
protected String KEY_CHANNEL_EVENT2="";
protected String KEY_CHANNEL_EVENT3="";
//Used for Slide
private int maxNumberOfCommand = 6;
private static final int LONGEST_COMMAND_POSSIBLE_TO_SEND = 1000;
private static final int MIN_INTERVAL = 15;
private static final int MAX_INTERVAL = 40;
private int LONG_PRESS_TIME;
private boolean LONG_PRESS_MULTIPLE = false;
protected boolean USE_MONKEY_FOR_PRESS = false;
protected boolean DONT_USE_MONKEY = false;
protected EventFilter eventFilter;
protected Thread recordingPhoneModethreadTouchscreen;
protected Thread recordingPhoneModethreadkeyboard;
protected Thread recordingPhoneModethreadkeyboard2;
protected Thread recordingPhoneModethreadkeyboard3;
protected long interval_between_event=70;
protected HashMap<String, Integer> keyMap;
private HashMap<String, Position> softkeyMap;
protected HashMap<String, String> keyCanal;
protected AndroidConfHandler gestionnaire;
/**
* TEST:TODO verify that's the good solution.
*
* @param phoneModel name of the phone to initialize.
* @throws PhoneException when phone isn't implemented.
*/
public AndroidDriver(String phoneModel, String version, IDevice d) throws PhoneException {
super(d);
adevice= d;
//read the initialization file
String confFileName = Platform.getInstance().getJATKPath()+Platform.FILE_SEPARATOR+"AndroidTools"+Platform.FILE_SEPARATOR+
"conf"+Platform.FILE_SEPARATOR+phoneModel;
File conffile = new File(confFileName+"_"+version+".xml");
if (!conffile.exists()) {
// try with just version number
conffile = new File(confFileName+"_"+version.substring(0, 3)+".xml");
if (!conffile.exists()) {
// try with just first number of version
conffile = new File(confFileName+"_"+version.substring(0, 1)+".xml");
if (!conffile.exists()) {
// try with just phone model name
conffile = new File(confFileName+".xml");
}
}
}
Logger.getLogger(this.getClass()).debug("Configuration file :"+conffile.getName());
if (!conffile.exists()) {
Logger.getLogger(this.getClass()).warn("No file found for configuration");
//new AndroidWizard(this, d,confFileName);
String defaultConfFileName = Platform.getInstance().getJATKPath()+Platform.FILE_SEPARATOR+"AndroidTools"+Platform.FILE_SEPARATOR+
"conf"+Platform.FILE_SEPARATOR+"default.xml";
File defaultconffile = new File(defaultConfFileName);
init(phoneModel,defaultconffile);
return;
} else init(phoneModel, conffile);
}
private void init(String phoneModel, File conffile) throws PhoneException {
mPhoneConfigFile = conffile.getName();
gestionnaire = new AndroidConfHandler();
try {
SAXParserFactory fabrique = SAXParserFactory.newInstance();
SAXParser parseur = fabrique.newSAXParser();
parseur.parse(conffile,gestionnaire);
} catch (Exception e) {
String error = ResourceManager.getInstance().getString("ANDROID_CONFIG_FILE_PARSING_ERROR", phoneModel+".xml");
ErrorManager.getInstance().addWarning(getClass().getName(), error, e);
throw new PhoneException(error);
}
keyMap = gestionnaire.getkeymap();
softkeyMap = gestionnaire.getsoftkeymap();
keyCanal = gestionnaire.getkeycanal();
try {
adevice.executeShellCommand("getevent",
new DetectChannelEventfilter(this,
gestionnaire.getKeyboardchannel(),
gestionnaire.getKeyboardSecondchannel(),
gestionnaire.getkeyboardThirdchannel(),
gestionnaire.getTouchscreenchannel() ));
} 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);
}
EVENT_KEY = "sendevent "+KEY_CHANNEL_EVENT+" 1 ";
EVENT_KEY2 = "sendevent "+KEY_CHANNEL_EVENT2+" 1 ";
EVENT_KEY3 = "sendevent "+KEY_CHANNEL_EVENT3+" 1 ";
if (gestionnaire.getXpattern()!=null) {
EVENT_Y = "sendevent "+MOUSE_CHANNEL_EVENT+" "+gestionnaire.getYpattern();
EVENT_X = "sendevent "+MOUSE_CHANNEL_EVENT+" "+gestionnaire.getXpattern();
}
if (gestionnaire.getXYpattern()!=null) {
EVENT_XY = "sendevent "+MOUSE_CHANNEL_EVENT+" "+gestionnaire.getXYpattern();
}
EVENT_MOUSE_DOWN = "sendevent "+MOUSE_CHANNEL_EVENT+" "+gestionnaire.getDownpattern()+";";
EVENT_MOUSE_UP = "sendevent "+MOUSE_CHANNEL_EVENT+" "+gestionnaire.getUppattern()+";";
if (gestionnaire.sendMouseDownForMove()) EVENT_MOUSE_MOVE = EVENT_MOUSE_DOWN;
String flush = gestionnaire.getFlushpattern();
if (flush != null) EVENT_FLUSH = "sendevent "+MOUSE_CHANNEL_EVENT+" "+flush+";";
String flush2 = gestionnaire.getFlush2pattern();
if (flush2 != null) EVENT_FLUSH2 = "sendevent "+MOUSE_CHANNEL_EVENT+" "+flush2+";";
SCREEN_RATIO_X = gestionnaire.getRatioX();
SCREEN_RATIO_Y = gestionnaire.getRatioY();
LONG_PRESS_TIME = gestionnaire.getLongPressTime();
LONG_PRESS_MULTIPLE = gestionnaire.getLongPressMultiple();
USE_MONKEY_FOR_PRESS = gestionnaire.useMonkeyForPress();
DONT_USE_MONKEY = gestionnaire.dontUseMonkey();
// We determince the size of the commands to know the maximum number of command
// we can send at once using executeShellCommand
int lengthCommand = LONGEST_COMMAND_POSSIBLE_TO_SEND;
lengthCommand -= getMouseDownCommand(100,100).length();
lengthCommand -= getMouseUpCommand(100,100).length();
int mouseMoveCommandLenght = getMouseMoveCommand(100,100).length();
Logger.getLogger(this.getClass() ).debug("getMouseMoveCommand(100,100): " + getMouseMoveCommand(100,100));
maxNumberOfCommand = lengthCommand/mouseMoveCommandLenght;
Logger.getLogger(this.getClass() ).debug("Number of commands we can send at once using executeShellCommand: " +
(maxNumberOfCommand+2));
this.setDisabledPhone(false);
}
protected String getMouseDownCommand(int x, int y) {
if (gestionnaire.sendMouseEventFirst()) {
if (gestionnaire.sendSeparateFlush())
return (EVENT_MOUSE_DOWN+
EVENT_FLUSH+
getXYCommand(x,y)+
EVENT_FLUSH2+
EVENT_FLUSH);
else return (EVENT_MOUSE_DOWN+
getXYCommand(x,y)+
EVENT_FLUSH2+
EVENT_FLUSH);
} else {
if (gestionnaire.sendSeparateFlush())
return (getXYCommand(x,y)+
EVENT_FLUSH2+
EVENT_FLUSH+
EVENT_MOUSE_DOWN+
EVENT_FLUSH);
else return (getXYCommand(x,y)+
EVENT_MOUSE_DOWN+
EVENT_FLUSH2+
EVENT_FLUSH);
}
}
protected String getMouseMoveCommand(int x, int y) {
if (gestionnaire.sendMouseEventFirst()) {
if (gestionnaire.sendSeparateFlush() && !EVENT_MOUSE_MOVE.equals(""))
return (EVENT_MOUSE_MOVE+
EVENT_FLUSH+
getXYCommand(x,y)+
EVENT_FLUSH2+
EVENT_FLUSH);
else return (EVENT_MOUSE_MOVE+
getXYCommand(x,y)+
EVENT_FLUSH2+
EVENT_FLUSH);
} else {
if (gestionnaire.sendSeparateFlush() && !EVENT_MOUSE_MOVE.equals(""))
return (getXYCommand(x,y)+
EVENT_FLUSH2+
EVENT_FLUSH+
EVENT_MOUSE_MOVE+
EVENT_FLUSH);
else return (getXYCommand(x,y)+
EVENT_MOUSE_MOVE+
EVENT_FLUSH2+
EVENT_FLUSH);
}
}
protected String getMouseUpCommand(int x, int y) {
if (gestionnaire.sendMouseEventFirst()) {
if (gestionnaire.sendSeparateFlush())
return (EVENT_MOUSE_UP+
EVENT_FLUSH+
getXYCommand(x,y)+
EVENT_FLUSH2+
EVENT_FLUSH);
else return (EVENT_MOUSE_UP+
getXYCommand(x,y)+
EVENT_FLUSH2+
EVENT_FLUSH);
} else {
if (gestionnaire.sendSeparateFlush())
return (getXYCommand(x,y)+
EVENT_FLUSH2+
EVENT_FLUSH+
EVENT_MOUSE_UP+
EVENT_FLUSH);
else return (getXYCommand(x,y)+
EVENT_MOUSE_UP+
EVENT_FLUSH2+
EVENT_FLUSH);
}
}
private String getXYCommand(int x, int y) {
String XY_COMMAND = "";
if (!EVENT_XY.equals(""))
XY_COMMAND = EVENT_XY+((int)((((((float)x*SCREEN_RATIO_X)+32768)*65536)+((float)y*SCREEN_RATIO_Y))))+";";
else XY_COMMAND = EVENT_X+((int)((float)x*SCREEN_RATIO_X))+";"+EVENT_Y+((int)((float)y*SCREEN_RATIO_Y))+";";
return XY_COMMAND;
}
@Override
public void keyDown(String key) throws PhoneException {
int code = keyMap.get(key);
if (keyCanal.get(key).equals("keyboard")) executeShellCommand(EVENT_KEY+code+" 1", false);
else if (keyCanal.get(key).equals("keyboard2")) executeShellCommand(EVENT_KEY2+code+" 1", false);
else executeShellCommand(EVENT_KEY3+code+" 1", false);
}
@Override
public void keyPress(String key, int keyPressTime, int delay) throws PhoneException {
if (keyMap.get(key)==null) super.keyPress(key, keyPressTime, delay);
else {
long time = System.currentTimeMillis();
//Logger.getLogger(this.getClass() ).debug("receive key " +key);
keyDown(key);
long timeSleep = keyPressTime - (System.currentTimeMillis()- time);
if (timeSleep>0) {
try {
Thread.sleep(timeSleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
keyUp(key);
}
}
@Override
public void keyUp(String key) throws PhoneException {
int code = keyMap.get(key);
if (keyCanal.get(key).equals("keyboard")) executeShellCommand(EVENT_KEY+code+" 0", false);
else if (keyCanal.get(key).equals("keyboard2")) executeShellCommand(EVENT_KEY2+code+" 0", false);
else executeShellCommand(EVENT_KEY3+code+" 0", false);
}
@Override
public void mouseDown(int x, int y) throws PhoneException {
//Logger.getLogger(this.getClass() ).debug("MOUSEDOWN("+x+","+y+")");
//Logger.getLogger(this.getClass() ).debug("MOUSEDOWN="+getMouseDownCommand(x,y));
executeShellCommand(getMouseDownCommand(x,y), false);
}
@Override
public void mouseUp(int x, int y) throws PhoneException {
//Logger.getLogger(this.getClass() ).debug("MOUSEUP("+x+","+y+")");
//Logger.getLogger(this.getClass() ).debug("MOUSEUP="+getMouseUpCommand(x,y));
executeShellCommand(getMouseUpCommand(x,y), false);
}
public void mouseMove(int x, int y) throws PhoneException {
//Logger.getLogger(this.getClass() ).debug("MOUSEMOVE("+x+","+y+")");
//Logger.getLogger(this.getClass() ).debug("MOUSEMOVE="+getMouseMoveCommand(x,y));
executeShellCommand(getMouseMoveCommand(x,y), false);
}
@Override
public void touchScreenPress(Position click) throws PhoneException {
int x = click.getX();
int y = click.getY();
mouseDown(x, y);
//Some phones need to get multiple mouseMove to simulate a long press.
if(!LONG_PRESS_MULTIPLE){
try {
Thread.sleep(click.getTime());
//Logger.getLogger(this.getClass() ).debug("Sleep: "+click.getTime()+"ms");
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
Long startTime = System.currentTimeMillis();
int delta = 1;
do{
try {
Thread.sleep(200);
//Logger.getLogger(this.getClass() ).debug("Sleep: 200ms - Time: "+(System.currentTimeMillis()-startTime));
} catch (InterruptedException e) {
e.printStackTrace();
}
if((System.currentTimeMillis()-startTime)>click.getTime())
break;
//We need to change the coordinates (else the command will be ignored)
delta = -delta;
executeShellCommand(getMouseMoveCommand(x += delta,y),false);
}while(true);
}
mouseUp(x, y);
}
@Override
/**
* Perform an optimized slide sending the whole command at once when possible
* in the limit of the maximum command size.
* @param path
* @throws PhoneException
*/
public void touchScreenSlide(List<Position> path) throws PhoneException {
if(path.size()<2)
throw new PhoneException("No enough coordinate in path");
int lastX,lastY;
int minDistance;
int maxDiff = Math.max(Math.abs(path.get(path.size()-1).getX()-path.get(0).getX()),
Math.abs(path.get(path.size()-1).getY()-path.get(0).getY()));
minDistance = maxDiff/maxNumberOfCommand;
if (minDistance>MAX_INTERVAL){
minDistance = MAX_INTERVAL;
}
if (minDistance<MIN_INTERVAL){
minDistance = MIN_INTERVAL;
}
//Logger.getLogger(this.getClass() ).debug("MaxDiff="+maxDiff+" - MinDistance="+minDistance);
int numberOfCommand = 0;
lastX = path.get(0).getX();
lastY = path.get(0).getY();
String command = getMouseDownCommand(lastX,lastY);
numberOfCommand++;
//Logger.getLogger(this.getClass() ).debug("Add command mouse Down : X="+lastX+" - Y="+lastY);
for (int i=1 ; i< path.size()-1 ;i++) {
if((Math.abs(lastX-path.get(i).getX())>minDistance)||
(Math.abs(lastY-path.get(i).getY())>minDistance)){
lastX = path.get(i).getX();
lastY = path.get(i).getY();
command += getMouseMoveCommand(lastX,lastY);
numberOfCommand++;
//Logger.getLogger(this.getClass() ).debug("Add command mouse Move : X="+lastX+" - Y="+lastY);
if(numberOfCommand==maxNumberOfCommand){
executeShellCommand(command,false);
command = "";
numberOfCommand = 0;
}
}
}
command += getMouseUpCommand(path.get(path.size()-1).getX(),path.get(path.size()-1).getY());
//Logger.getLogger(this.getClass() ).debug("Add command mouse Up : X="+lastX+" - Y="+lastY);
executeShellCommand(command,false);
}
@Override
public void touchScreenDragnDrop(List<Position> path) throws PhoneException {
if(path.size()<2)
throw new PhoneException("No enough coordinate in path");
int lastX,lastY;
int minDistance;
int maxDiff = Math.max(Math.abs(path.get(path.size()-1).getX()-path.get(0).getX()),
Math.abs(path.get(path.size()-1).getY()-path.get(0).getY()));
minDistance = maxDiff/maxNumberOfCommand;
if (minDistance>MAX_INTERVAL){
minDistance = MAX_INTERVAL;
}
if (minDistance<MIN_INTERVAL){
minDistance = MIN_INTERVAL;
}
//Logger.getLogger(this.getClass() ).debug("MaxDiff="+maxDiff+" - MinDistance="+minDistance);
int numberOfCommand = 0;
lastX = path.get(0).getX();
lastY = path.get(0).getY();
long lastTime = path.get(0).getTime();
long lastTimePlayed = System.currentTimeMillis();
//Logger.getLogger(this.getClass() ).debug("Add command mouse Down : X="+lastX+" - Y="+lastY);
String command = getMouseDownCommand(lastX,lastY);
executeShellCommand(command,false);
//Some phones need to get multiple mouseMove to simulate a long press.
if(!LONG_PRESS_MULTIPLE){
try {
Thread.sleep(LONG_PRESS_TIME);
//Logger.getLogger(this.getClass() ).debug("Sleep: "+LONG_PRESS_TIME+"ms");
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
Long startTime = System.currentTimeMillis();
int delta = 1;
do{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if((System.currentTimeMillis()-startTime)>LONG_PRESS_TIME)
break;
//We need to change the coordinates (else the command will be ignored)
lastX += delta;
delta = -delta;
command = getMouseMoveCommand(lastX,lastY);
executeShellCommand(command,false);
}while(true);
}
command = "";
for (int i=1 ; i< path.size() ;i++) {
long diffTime = path.get(i).getTime()-lastTime;
long diffReal= System.currentTimeMillis()-lastTimePlayed;
if((Math.abs(lastX-path.get(i).getX())>minDistance)||
(Math.abs(lastY-path.get(i).getY())>minDistance)||
(i==(path.size()-1))){
lastX = path.get(i).getX();
lastY = path.get(i).getY();
command += getMouseMoveCommand(lastX,lastY);
//Logger.getLogger(this.getClass() ).debug("Add command mouse Move : X="+lastX+" - Y="+lastY);
numberOfCommand++;
if(numberOfCommand==maxNumberOfCommand){
lastTime = path.get(i).getTime();
lastTimePlayed = System.currentTimeMillis();
//Logger.getLogger(this.getClass() ).debug("DiffTime= "+diffTime+" - DiffReal= "+diffReal);
executeShellCommand(command,false);
//If we are too fast, we add some sleep
if((diffTime-diffReal)>100){
try {
//Logger.getLogger(this.getClass() ).debug("Sleep: "+(diffTime-diffReal));
Thread.sleep(diffTime-diffReal);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
command = "";
numberOfCommand = 0;
}
}
}
command += getMouseUpCommand(path.get(path.size()-1).getX(),path.get(path.size()-1).getY());
//Logger.getLogger(this.getClass() ).debug("Add command mouse Up : X="+(path.get(path.size()-1).getX())+" - Y="+(path.get(path.size()-1).getY()));
executeShellCommand(command,false);
}
@Override
public void stopRecordingMode() {
if(recordingPhoneModethreadTouchscreen !=null && recordingPhoneModethreadTouchscreen.getState() != Thread.State.TERMINATED ) {
((RecordingThread) recordingPhoneModethreadTouchscreen).stoprecording();
if (gestionnaire.useSmartTouchDetection() && eventFilter!=null) ((SmartTouchScreenEventfilter)eventFilter).stopDetection();
}
if(recordingPhoneModethreadkeyboard !=null && recordingPhoneModethreadkeyboard.getState() != Thread.State.TERMINATED )
((RecordingThread) recordingPhoneModethreadkeyboard).stoprecording();
if(recordingPhoneModethreadkeyboard2 !=null && recordingPhoneModethreadkeyboard2.getState() != Thread.State.TERMINATED )
((RecordingThread) recordingPhoneModethreadkeyboard2).stoprecording();
if(recordingPhoneModethreadkeyboard3 !=null && recordingPhoneModethreadkeyboard3.getState() != Thread.State.TERMINATED )
((RecordingThread) recordingPhoneModethreadkeyboard3).stoprecording();
isScriptRecording = false;
}
protected EventFilter createTouchScreenEventFilter(AndroidDriver d, AndroidConfHandler gestionnaire, HashMap<String,Position> softkeyMap){
return new DefaultTouchScreenEventfilter(d,gestionnaire, softkeyMap);
}
protected EventFilter createKeyboardEventFilter(AndroidPhone aphone,Map<Integer, String> codemap){
return new DefaultKeyboardEventfilter(aphone, codemap);
}
public void startRecordingMode() throws PhoneException {
isScriptRecording = true;
//touchscreen
//filter in mouse move at least 10px of difference
if (gestionnaire.useSmartTouchDetection()) eventFilter = new SmartTouchScreenEventfilter(this,gestionnaire, softkeyMap);
else{
Logger.getLogger(this.getClass() ).debug("Creating touchScreen Event filter");
eventFilter = createTouchScreenEventFilter(this,gestionnaire, softkeyMap);
}
recordingPhoneModethreadTouchscreen = new RecordingThread(adevice,
MOUSE_CHANNEL_EVENT,
eventFilter);
recordingPhoneModethreadTouchscreen.start();
//keyboard
//invert keymap
final HashMap<Integer,String> keycodeMap = new HashMap<Integer, String>();
for (String key : keyMap.keySet())
keycodeMap.put(keyMap.get(key), key);
recordingPhoneModethreadkeyboard = new RecordingThread(adevice,
KEY_CHANNEL_EVENT,
createKeyboardEventFilter(this, keycodeMap) );
recordingPhoneModethreadkeyboard.start();
if (KEY_CHANNEL_EVENT2!=null) {
recordingPhoneModethreadkeyboard2 = new RecordingThread(adevice,
KEY_CHANNEL_EVENT2,
createKeyboardEventFilter(this, keycodeMap) );
recordingPhoneModethreadkeyboard2.start();
}
if (KEY_CHANNEL_EVENT3!=null) {
recordingPhoneModethreadkeyboard3 = new RecordingThread(adevice,
KEY_CHANNEL_EVENT3,
createKeyboardEventFilter(this, keycodeMap));
recordingPhoneModethreadkeyboard3.start();
}
return;
}
}