Okay, so I am working on an app that will auto accept lyft request, but I am having a problem with my code not using performAction(AccessibilityNodeInfo.ACTION_CLICK); correctly.
public class AutoService extends AccessibilityService {
private static LyftAdapter lyftAdapter = new LyftAdapter();
// Automated Service (onAccessibilityEvent)
#TargetApi(16)
#Override
public void onAccessibilityEvent(AccessibilityEvent event)
{
AccessibilityNodeInfo source = event.getSource();
String lyftPackage = "com.lyft.android.driver";
String packageName = Tools.getPackage(source);
if (!packageName.equals(lyftPackage))
{
event.recycle();
return;
}
if (source == null)
{
event.recycle();
return;
}
processUI(event.getSource());
}
public void processUI(AccessibilityNodeInfo source)
{
source = getRootInActiveWindow();
if (Tools.getPackage(source).equals("com.lyft.android.driver") || Tools.getPackage(source).equals("me.lyft.android"))
{
if (!Lyft_Status.equals("OFFLINE"))
{
lyftAdapter.processEvent(source);
}
else
{
Log.v(TAG, "Can't process UI: " + Lyft_Status);
}
}
if (source != null)
source.recycle();
}
}
public abstract class RideshareAdapter {
public void processEvent(final AccessibilityNodeInfo source)
{
final StringBuilder sb = new StringBuilder();
processSubEvent(source, 0, sb);
final String string = sb.toString();
if (string == null)
{
Log.v(TAG, "String is NULL");
return;
}
processUIText(source, string.toLowerCase());
}
// PROCESS SECONDARY EVENT
private void processSubEvent(final AccessibilityNodeInfo source, final int n, final StringBuilder sb) {
for (int i = 0; i < n; ++i) {
sb.append("\t");
}
if (source != null)
{
sb.append(Tools.getText(source));
sb.append("\n");
final int childCount = source.getChildCount();
for (int j = 0; j < childCount; ++j) {
final AccessibilityNodeInfo child = source.getChild(j);
processSubEvent(child, n + 1, sb);
if (child != null) {
child.recycle();
}
}
}
}
// CLICK THE SCREEN
protected void clickScreen(AccessibilityNodeInfo source, final String text)
{
final AccessibilityNodeInfo s = source;
new Handler().postDelayed(new Runnable() {
List<AccessibilityNodeInfo> list = s.findAccessibilityNodeInfosByText(text);
#Override
public void run() {
for (final AccessibilityNodeInfo node : list) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}, 1000);
}
}
public class LyftAdapter
extends RideshareAdapter
{
// LYFT ADAPTER
protected void processUIText(AccessibilityNodeInfo source, String text)
{
// RIDE REQUEST
if (text.contains("tap here to accept"))
{
clickScreen(source, "Tap here to accept");
{
}
The string comes out as (Just like it is shown):
Lyft
11 mins
away
Passenger Name
New
Tap here to accept
But for some reason, it triggers saying it is going to click on "Tap here to accept" textview, but it never actually does it. Any suggestions?
To be completely honest, your post is very difficult to read. You have functions that you have defined purely for organizational purposes and not because they are meant to be re-used. It makes it very difficult to parse and understand over the course of a StackOverflow post... Yet you did not provide enough for me to copy and paste and make sense of in Android Studio.
When you post code on StackOverflow you should go for a minimal replicating example and you ABSOLUTELY should remove your random Log calls. You may need them to help you understand what's happening, but hopefully WE do not :) and they just clutter things and make it more difficult to read your code. THIS BEING SAID, allow me to focus on one bit,
Note that I have cleaned up some of the poor style and debugging statements. Answers are in the code comments!
protected void clickScreen(final AccessibilityNodeInfo source, final String text)
{
new Handler().postDelayed(new Runnable() {
//Find ALL of the nodes that match the "text" argument.
List<AccessibilityNodeInfo> list = source.findAccessibilityNodeInfosByText(text);
#Override
public void run() {
//Non discrliminintly click them, whether they're buttons, or text fields or links... just click them and hope they do something.
for (final AccessibilityNodeInfo node : list) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
//Delay it for a second AFTER the function has been called for no particularly good reason besides perhaps invalidating all of the nodes in the heirarchy... GOOD CALL!
}, 1000);
}
Given the above issues and the aforementioned generic code quality issues, it is difficult to provide a concise answer. This post leaves too many potential issues. Any provided answer would be a stab in the dark. I find it MOST likely that the problem is covered in my code comments, but it could most definitely be elsewhere. Also, my apologies for the sass!
All this being said, you might try this version of the function!
static void clickFirstMatchingNode(AccessibilityService service, final String text) {
final List<AccessibilityNodeInfo> list = service.getRootInActiveWindow().findAccessibilityNodeInfosByText(text);
for (AccessibilityNodeInfo node : list) {
//Check if the action completely successfully. Also, only click one of them. This is kind of an assumption, it also simplifies the logic. You can certainly write a version of this that clicks everything that matches!
if (node.performAction(AccessibilityNodeInfo.ACTION_CLICK)) return;
}
//If no node is successfully clicked Log some stuff!
Log.wtf(YourService.class.getName(), "Failed to click any nodes! WTF?: " + text);
}
NOTE: None of the above mentioned anything to do with your use of Accessibility APIs! I think that that is interesting.
Related
I'm building a custom Android TextToSpeechService which filters text by language.
Currently, I'm handling non-English-language text with my custom TextToSpeech service, and I'm sending English language text to an instance of Google Speech Services.
My problem is that, when I use Google Talkback, I'm no longer able to receive Talkback's usage hints (e.g. "Double-tap to activate", etc.)
The following code runs on a singleton which is called from my TextToSpeechService.
private TextToSpeech mAndroidTTS = null;
public void initEnglishTTS() {
if (mAndroidTTS == null) {
mAndroidTTS = new TextToSpeech(mContext, i -> {
if (i == TextToSpeech.SUCCESS) {
mAndroidTTS.setLanguage(Locale.US);
}
}, "com.google.android.tts");
}
}
public void synthesize(String text, SynthesisCallback callback) {
callback.start(SAMPLING_RATE_HZ,
AudioFormat.ENCODING_PCM_16BIT, 1);
sayInEnglish(text); // If I comment out this line, then it works
callback.done();
}
protected static void sayInEnglish(String text) {
String utteranceID = UUID.randomUUID().toString();
mAndroidTTS.speak(text, TextToSpeech.QUEUE_FLUSH, null, utteranceID);
}
In my custom TextToSpeechService, I have the following code:
public class CustomTtsService extends TextToSpeechService {
// an instance of the singleton
private SynthesizerSingleton singleton = singleton.getInstance();
#Override
protected void onSynthesizeText(SynthesisRequest request, SynthesisCallback callback) {
String text = getRequestString(request);
singleton.synthesize(text, callback);
}
private String getRequestString(SynthesisRequest request) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return request.getCharSequenceText().toString();
} else {
return request.getText();
}
}
}
If I were to comment out the line sayInEnglish(text), then all of the expected requests are received--but obviously, no utterances are synthesized.
What could I be doing incorrectly?
EDIT: The whole pipeline fails silently, neither the service nor the text string are null.
I am trying to refactor old SimpleFormController. I would like to replace getSuccessView() and gerFormView() calls with actual success view and form view Strings.
I went through https://spoon.gforge.inria.fr/first_transformation.html, it shows how to generate and add statements however I could not understand how to modify.
I have tried couple of things.
Replace statements with the getSuccessView() and getFormView() calls
public class SimpleFormControllerReplaceViewCall extends AbstractProcessor<CtMethod> {
MetaData meta;
String successView= "successView";
String formView = "formView";
public SimpleFormControllerReplaceViewCall(MetaData meta) {
this.meta = meta;
}
#Override
public boolean isToBeProcessed(CtMethod candidate) {
if(candidate.getBody() == null) { //Ignore abstract methods
return false;
}
String sourceCode;
try {
sourceCode = candidate.getBody()
.getOriginalSourceFragment()
.getSourceCode();
} catch (Exception e) {
return false;
}
return sourceCode.contains(getViewFunctionName(successView))
|| sourceCode.contains(getViewFunctionName(formView));
}
#Override
public void process(CtMethod method) {
Node beanNode = getBeanNode(method);
CtBlock<Object> body = getFactory().createBlock();
method.getBody().getStatements()
.stream()
.map(s -> {
Optional<String> sourceCode = getStatementSourceCode(s);
if(!sourceCode.isPresent()) {
return s.clone(); // Clone required to handle runtime error for trying attach a node to two parents
} else {
System.out.println("Modifying: " + method.getSignature());
String code = sourceCode.get();
code = replaceViewCalls(beanNode, code, successView);
code = replaceViewCalls(beanNode, code, formView);
return getFactory().createCodeSnippetStatement(code);
}
}).forEach(body::addStatement);
method.setBody(body);
}
private Optional<String> getStatementSourceCode(CtStatement s) {
String sourceCode = null;
try {
sourceCode = s.getOriginalSourceFragment()
.getSourceCode();
} catch (Exception e) {}
System.out.println(sourceCode);
if (sourceCode != null &&
(sourceCode.contains(getViewFunctionName(successView))
|| sourceCode.contains(getViewFunctionName(formView)))) {
sourceCode = sourceCode.trim();
if(sourceCode.endsWith(";"))
sourceCode = sourceCode.substring(0, sourceCode.length()-1);
return Optional.of(sourceCode);
} else {
return Optional.empty();
}
}
public String replaceViewCalls(Node beanNode, String code, String viewType) {
String getViewFunctionName = getViewFunctionName(viewType);
if (!code.contains(getViewFunctionName)) {
return code;
}
String view = AppUtil.getSpringBeanPropertyValue(beanNode, viewType);
return code.replaceAll(getViewFunctionName + "\\(\\)", String.format("\"%s\"", view));
}
public Node getBeanNode(CtMethod method) {
String qualifiedName = method.getParent(CtClass.class).getQualifiedName();
return meta.getFullyQualifiedNameToNodeMap().get(qualifiedName);
}
private String getViewFunctionName(String viewType) {
return "get" + viewType.substring(0, 1).toUpperCase() + viewType.substring(1);
}
}
This however adds unwanted at end of blocks if() {... }; This creates syntax errors when if {} else {} blocks contain return statement(s). Auto import is turned on and imports are not added when there is more one class with same name (e.g., Map is present in classpath from few libraries) - this is consistent with the document. Can this be avoided when refactoring code? Original java file has correct imports.
Another approach I tried is to directly manipulate the body as a whole.
#Override
public void process(CtMethod method) {
String code = method.getBody()
.getOriginalSourceFragment()
.getSourceCode();
Node beanNode = getBeanNode(method);
code = replaceViewCalls(beanNode, code, successView);
code = replaceViewCalls(beanNode, code, formView);
CtCodeSnippetStatement codeStatement = getFactory().createCodeSnippetStatement(code);
method.setBody(codeStatement);
}
this still has same auto import issue as first one. Apart from that it adds redundant curly braces, for examples
void method() { x=y;}
will become
void method() { {x=y;} }
That that will be pretty printed ofcourse.
Also javadocs for getOriginalSourceFragment() also has below warning
Warning: this is a advanced method which cannot be considered as part
of the stable API
One more thing I thought of doing is creating pattern for each type of usage of getSuccessView() like
viewName = getSuccessView();
return getSuccessView();
return ModelAndView(getSuccessView(), map); etc, however for that I will have to write a whole bunch of processors / templates.
Since it is simple replacement, easiest is do something like below
//Walk over all files and execute
Files.lines(Paths.get("/path/to/java/file"))
.map(l -> l.replaceAll("getSuccessView\\(\\)", "actualViewNameWithEscapedQuotes"))
.map(l -> l.replaceAll("getFormView\\(\\)", "actualViewNameWithEscapedQuotes"))
.forEach(l -> {
//write to file
});
Since I can avoid text manipulation with the help of spoon for things like changing modifiers, annotations, method name, annotations etc, I am hoping there should be a better way to modify the method body.
You should treat the processor input as an abstract syntax tree instead of a string:
public class SimpleFormControllerReplaceViewCall extends AbstractProcessor<CtMethod<?>> {
#Override
public boolean isToBeProcessed(CtMethod candidate) {
if(candidate.isAbstract()) { //Ignore abstract methods
return false;
}
return !candidate.filterChildren((CtInvocation i)->
i.getExecutable().getSimpleName().equals("getSuccessView")
|| i.getExecutable().getSimpleName().equals("getFormView")).list().isEmpty();
}
#Override
public void process(CtMethod<?> ctMethod) {
Launcher launcher = new Launcher();
CodeFactory factory = launcher.createFactory().Code();
List<CtInvocation> invocations = ctMethod.filterChildren((CtInvocation i)->
i.getExecutable().getSimpleName().equals("getSuccessView")
|| i.getExecutable().getSimpleName().equals("getFormView")).list();
for(CtInvocation i : invocations) {
if(i.getExecutable().getSimpleName().equals("getSuccessView")) {
i.replace(factory.createLiteral("successView"));
} else {
i.replace(factory.createLiteral("formView"));
}
}
}
}
Here the CtMethod AST is traversed in search for CtInvocation elements with the specified properties. The found elements are then replaced with new string literal elements.
I'm bit confused. I have the following:
public static String showInputDialog() {
Form frm = new Form();
final Command cmd = new Command("Ok");
final TextField txt = new TextField("Enter the text", null, 1024, 0);
frm.addCommand(cmd);
frm.append(txt);
frm.setCommandListener(new CommandListener() {
public void commandAction(Command c, Displayable d) {
if (c == cmd) {
return txt.getString(); // Error !!
} else {
return null; // Error !!
}
}
});
}
As you can see, I want to return the input dialog string, while the anonymous class method should return void. How can I resolve this problem?
This does not work as you expected.
I see there are already some solutions, but I feel a bit more discussion about what is actually going on might be helpful.
When you call the frm.setCommandListener(new CommandListener() { ... }) the code presents the user with a dialog where she can type in some text and submit, but the code does not stop and wait until the user finishes.
Instead the code continues to execute - without yielding the result. Only after the user finished typing and submits, you get called back to process the result - which might happen much later, or not at all.
I guess you have some code calling this method like:
public void someMethod(int foo, String bar) {
[...]
String result = MyInputForm.showInputDialog();
// do something with the result
System.out.println("hey, got a result "+ result);
[...]
}
Instead you need to reorganize this. First write a helper class handling the result:
public static class MyCallBack {
public MyCallBack(... /* here pass in what you need to process the result*/) {
... remember necessary stuff in instance variables
}
public void processResult(String result) {
// do something with the result
System.out.println("hey, got a result "+ result);
[...]
}
}
then the calling side does just:
public void someMethod(int foo, String bar) {
[...]
MyInputForm.showInputDialog( new MyCallBack(... here pass in stuff ...) );
[...]
}
and the actual code has to be changed to:
public static String showInputDialog(final MyCallBack callback) {
Form frm = new Form();
final Command cmd = new Command("Ok");
final TextField txt = new TextField("Enter the text", null, 1024, 0);
frm.addCommand(cmd);
frm.append(txt);
frm.setCommandListener(new CommandListener() {
public void commandAction(Command c, Displayable d) {
if (c == cmd) {
return callback.processResult(txt.getString());
} else {
return; // or just omit the else part
}
}
});
}
Two issues:
this way of programming feels pretty backwards, but it is really the way it works.
what feels not right is that I need to define a second helper class aside of the CommandListener. That is really not good style. I hope it can be improved, but as I do not see the complete code (which would be too much information anyway), I have to leave it to you to improve the code and get rid of the clutter. While I feel you want to have a modular, reusable input dialog helper, this might not be the best approach; better define the Form,TextField and Command directly where you need the result and get that running. Make it reusable in a second step after you get it running.
You don't need to return it if you instead do something with the String or store it somewhere, for example:
static String result;
public String commandAction(Command c, Displayable d) {
if (c == cmd) {
result = txt.getString();
} else {
result = null;
}
}
Although you'll have threading issues to deal with.
Given that CommandListener is fixed, 2 possible options are
Use a class member variable in the outer class & assign to that variable instead
private static String myText;
...
public static String showInputDialog() {
...
frm.setCommandListener(new CommandListener() {
public void commandAction(Command c, Displayable d) {
if (c == cmd) {
myText = txt.getString();
} else {
myText = null;
}
}
});
}
or Create a concrete implementation of your CommandListener and set the return value as a property of the new implementation
I would have a look at making the method/variable in this snippet non-static...
You cant return the string because you dont know when the listener will be called.
You can do something with it once you have the string though.
public static void showInputDialog() {
StringHandler sh = new StringHandler();
frm.setCommandListener(new CommandListener() {
public void commandAction(Command c, Displayable d) {
if (c == cmd) {
sh.handle(txt.getString());
} else {
sh.handle(null);
}
}
});}
public class StringHandler {
public void handle(String s){
// Do something with that string.
}
}
#Before public void setUp() {
Robot robot = BasicRobot.robotWithCurrentAwtHierarchy();
ApplicationLauncher.application("myApp").start();
Pause.pause(5, TimeUnit.SECONDS);
frame = WindowFinder.findFrame("frame0").using(robot);
JTableFixture table = frame.table(new GenericTypeMatcher<JTable>(JTable.class) {
#Override protected boolean isMatching(JTable table) {
return (table instanceof myTreeTable);
}
});
}
This code works well. If we remove the 5 seconds pause, then the table is not found because it takes some seconds to the app to load it.
I would like to know if there is a cleaner way of doing it. I tried with robot.waitForIdle() after ApplicationLauncher (I guess once EDT is empty, everything is loaded), but it just doesn´t work.
I know pause can use some conditions as an event on when to stop, but I don´t understand how to write it since JavaDoc and official doc is poor.
Pause.pause(WaitForComponentToShowCondition.untilIsShowing(frame.component())) : I need a component, if I pass the wrapper frame it does not work. And I cannot pass the table because thats precisely what I am waiting for to get.
I understand then I should probably work with ComponentFoundCondition but I dont get it! I tired with:
ComponentMatcher matcher = new GenericTypeMatcher<JTable>(JTable.class) {
#Override protected boolean isMatching(JTable table) {
return (table instanceof myTreeTable);
}
};
Pause.pause(new ComponentFoundCondition("DebugMsg", frame.robot.finder(), matcher));
Any help?
You could use ComponentFinder to locate the component. For example, based on the matcher in the question:
final ComponentMatcher matcher = new TypeMatcher(myTreeTable.class);
Pause.pause(new Condition("Waiting for myTreeTable") {
#Override
public boolean test() {
Collection<Component> list =
window.robot.finder().findAll(window.target, matcher);
return list.size() > 0;
}
}, 5000);
Here is an alternative with lookup by name:
final ComponentMatcher nameMatcher = new ComponentMatcher(){
#Override
public boolean matches(Component c) {
return "ComponentName".equals(c.getName()) && c.isShowing();
}
};
Pause.pause(new Condition("Waiting") {
#Override
public boolean test() {
Collection<Component> list =
window.robot.finder().findAll(window.target, nameMatcher);
return list.size() > 0;
}
}, 5000);
I currently have code to share a variable between two entry points in my application. The variable is the iconCount variable used to indicate how many notices the user has which is displayed on the home screen beside the icon. The way I've managed to do this is with a singleton and it (seems) to work fine at the moment. The issue is now that I do not want those notices to reset to zero when I completely turn off and turn on the phone. Should there be 7 notifications, I want there to be 7 notifications even after a device restart. For this I apparently need a persistent store integration which I've researched for a while.
So far my code for the bare singleton is:
public class MyAppIndicator{
public ApplicationIndicator _indicator;
public static MyAppIndicator _instance;
MyAppIndicator () {
setupIndicator();
}
public static MyAppIndicator getInstance() {
if (_instance == null) {
_instance = new MyAppIndicator ();
}
return(_instance);
}
public void setupIndicator() {
//Setup notification
if (_indicator == null) {
ApplicationIndicatorRegistry reg = ApplicationIndicatorRegistry.getInstance();
_indicator = reg.getApplicationIndicator();
if(_indicator == null) {
ApplicationIcon icon = new ApplicationIcon(EncodedImage.getEncodedImageResource ("notificationsdemo_jde.png"));
_indicator = reg.register(icon, false, true);
_indicator.setValue(0);
_indicator.setVisible(false);
}
}
}
public void setVisible1(boolean visible, int count) {
if (_indicator != null) {
if (visible) {
_indicator.setVisible(true);
_indicator.setValue(count); //UserInterface.incrementCount()
} else {
_indicator.setVisible(false);
}
}
}
}
I have been using the blackberry tutorial to figure out how to implement the persistable storage: http://supportforums.blackberry.com/t5/Java-Development/Storing-persistent-data/ta-p/442747
Now before I go any further I must stress I'm very new to java development so my coding might be completely wrong, but here is what I've tried to do:
public void setVisible1(boolean visible, int count) {
if (_indicator != null) {
if (visible) {
_indicator.setVisible(true);
_indicator.setValue(count); //UserInterface.incrementCount()
StoreInfo info = new StoreInfo();
info.incElement();
synchronized (persistentCount) {
//persistentCount.setContents(_data);
persistentCount.commit();
}
} else {
_indicator.setVisible(false);
}
}
}
static {
persistentCount = PersistentStore.getPersistentObject(0xdec6a67096f833cL);
synchronized (persistentCount) {
if (persistentCount.getContents() == null) {
persistentCount.setContents(new Vector()); //don't know what to do with this?
persistentCount.commit();
}
}
}
private static final class StoreInfo implements Persistable{
private int iconCount;
public StoreInfo(){}
public int getElement(){
return (int)iconCount;
}
public void incElement(){
iconCount++; //persistently increment icon variable
}
public void resetElement(){
iconCount=0; //when user checks application
}
}
The code above doesn't work which I'd expect somehow because I'm having trouble implementing the persistent portion. If anyone has any idea or input on how to accomplish this any assistance would be helpful. And of course thanks in advance.
In the example they have a variable called _data that holds the StoreInfo class, so first of all you should be keeping the StoreInfo in some variable. To do this have something like the following in your static initializer:
persistentCount = PersistentStore.getPersistentObject(0xdec6a67096f833cL);
synchronized (persistentCount) {
if (persistentCount.getContents() == null) {
persistentCount.setContents(new StoreInfo());
persistentCount.commit();
}
}
_data = (StoreInfo)persistentCount.getContents();
Now when you want to update it and save to the PersistentStore you can have something like:
_data.incElement();
synchronized(persistentCount) {
persistentCount.setContents(_data);
persistentCount.commit();
}
Assuming you're going to only ever have one instance of StoreInfo it could be better to put the commit code into the modifier methods so you don't forget to save the new values to the PersistentStore.