Let's say I have a similar scenario:
I'm using an accessibility service in order to my TTS engine talk when this dialog appears, but the only thing I was able to detect were the selectable views (those pointed by the arrow).
Is there any way to detect the title and (more importantly) whole text inside the dialog?
Yes. I think it is likely that you're grabbing these items off of accessibility events, which focus on a single node. What you want to do instead is look at the entire view hierarchy. You can do this one of two ways. First thing to note is that Accessibility Nodes are a tree. Just like the view heirarchy is a tree. In fact, this tree matches the view hierarchy, almost 1 to 1. Developers can force an element to not be included in the view hierarchy, though this isn't done often in practice. Even if they do you can get this information regardless. Let's assume we want this information. First thing we want to do is make sure it's included.
protected void onServiceConnected() {
super.onServiceConnected();
AccessibilityServiceInfo tempInfo = getServiceInfo();
tempInfo.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
setServiceInfo(tempInfo);
}
It's highly likely you can skip this step, but just in case!
After this it's quite simple. First, so that you can see where this information is, let's write a cute little logging function.
public static void logNodeHeirarchy(AccessibilityNodeInfo nodeInfo, int depth) {
if (nodeInfo == null) return;
String logString = "";
for (int i = 0; i < depth; ++i) {
logString += " ";
}
logString += "Text: " + nodeInfo.getText() + " " + " Content-Description: " + nodeInfo.getContentDescription();
Log.v(LOG_TAG, logString);
for (int i = 0; i < nodeInfo.getChildCount(); ++i) {
logNodeHeirarchy(nodeInfo.getChild(i), depth + 1);
}
}
This function should log the entire tree of an accessibility node. Add it as a static function to your accessibility service. Now we just need to call it on the root node. You can easily change the properties that get logged. I find text, content description, and view id to be the most useful.
#Override
public void onAccessibilityEvent(AccessibilityEvent e) {
switch (e.getEventType()) {
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
logNodeHeirarchy(getRootInActiveWindow(), 0);
}
}
}
This should allow you to see where the information is. All you have to do is figure out how to parse it. Note: You can also crawl up from a leaf node, using getParent().
Related
Last week I started learning Android as I needed to create an application for one of the projects at Uni.
The application is a simple barcode/QRcode scanner and it should scan the code, compare its result with the database (I'm using Firebase) and either return other data from database if the barcode is found or ask the user if he wants to add the barcode to the database if it's not found.
I thought the easiest way to do it would be to use AlertDialog, but the app crashes every single time I scan the code.
I debugged the app and checked the Logcat, what I get is:
You need to use a Theme.AppCompat theme (or descendant) with this activity.
This is exactly where I get the error and where I wanted to use AlertDialog - based on the value in the variable details.
private BarcodeCallback callback = new BarcodeCallback() {
#Override
public void barcodeResult(final BarcodeResult result) {
barcodeView.decodeSingle(callback);
dbRef.child("Items").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Iterator<DataSnapshot> item = dataSnapshot.getChildren().iterator();
Boolean isFound = false;
while (!isFound || item == null) {
DataSnapshot i = item.next();
String check = i.child("ID").getValue().toString();
if (result.getText().equals(check)) {
isFound = true;
details = "Consumption: " + i.child("Consumption").getValue().toString()
+ "\nCost: " + i.child("Cost").getValue().toString()
+ "\nName: " + i.child("Name").getValue().toString();
} else {
details = "Not found";
}
}
new AlertDialog.Builder(getApplicationContext())
.setMessage("This is just an example for the purpose of the question.")
.create()
.show();
}
I get the error exactly on the line with .show();.
In the previous posts I found that you can't display AlertDialog in this place, and you need to use runOnUiThread function or Handler, none of those options worked for me, and I was getting the error in the same place.
Do you guys have any advice or suggestions?
Also, I'm sorry for the way this post looks like or for any missing but required information. I know it's not an excuse, but this is my first post here.
Thanks in advance for any replies.
The problem is here:
new AlertDialog.Builder(getApplicationContext())
You can't build a Dialog using the application context. To reach this you need an Activity Context.
Read this question or this article for further understanding
Right now i'm doing an educational app.I stucked with some problem.
My app first page has list of java programs name like below
swap two numbers
find biggest number
reverse number
When user press a particular item- it has to go to detail screen and has to display particular program (i mean user press on swap of two no's need to navigate to detail page to display respective program)->right now its going detail view controller
For example I have program like this
/********print star pattern ************/
import java.util.Scanner;
public class Diamond
{
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
System.out.println("Enter N : ");
int n=sc.nextInt();
System.out.print("Enter Symbol : ");
char c = sc.next().charAt(0);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n-i;j++)
{
System.out.print(" ");
}
for(int j=1;j<=i*2-1;j++)
{
System.out.print(c);
}
System.out.println();
}
for(int i=n-1;i>0;i--)
{
for(int j=1;j<=n-i;j++)
{
System.out.print(" ");
}
for(int j=1;j<=i*2-1;j++)
{
System.out.print(c);
}
System.out.println();
}
}
}
How can I print above program in detail in view controller ?
Now am using textview, Is it okay to continue with it or Is any other component that i could make use of ?because some programs have more width and height.
or else any other mechanism is available to achieve this?
I researched a lot but didn't find solution.
Can someone suggest me how to go with this? where to store the program? How to display it?
Thanks.
This is my code.In my custom AccessibilityService,I get all TextView by the method,onAccessibilityEvent.And then I want to mark the text.
public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityNodeInfo source = event.getSource();
if (source == null) {
return;
}
for (int i = 0; i < source.getChildCount(); i++) {
AccessibilityNodeInfo node = source.getChild(i);
if (TEXTVIEW.equals(node.getClassName())) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
node.getText().toString()+1);//just want to mark
node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
}
}
}
}
This bit of code works fine for me!
public void onAccessibilityEvent(AccessibilityEvent e) {
if (e.getSource() == null) return;
if (e.getSource().getClassName().equals(EditText.class.getName())) {
Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
"android");
e.getSource().performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
}
}
And happily places the text "android" into the editable text container! AH, now, if you want to change the text of a TextView, now, this is an illegal thing to try and do. The text of a TextView is a readonly property. You can't edit it. An accessibility node info is an accessibility services representation of a rendered view. You can't ask the App to change the text of a TextView. This has MASSIVE security implications. Oh, so you have a service that tells everyone that the Password field is the Username field and vice versa? Isn't that handy? Basically, you can only edit the text of a view that makes sense to be Editable.
That being said, you can tell the user that the text is whatever you want to tell them it is, when you provide feedback. Not that I would use an accessibility service that spoke "kitten" for every view in the hierarchy... but, your use case/what you want to accomplish is not clear. If you would provide another question that asks "This is what I want to accomplish..." instead of "this is what I tried, why doesn't it work?" I would happily help.
My app has one activity. The app has a drawer that has a list that is filled from my content provider. From the drawer the user can select an item and then the Activity will be filled with the appropriate content dynamically. I am not sure how to implement app indexing in such a case. I mean based on step 3 of the tutorial, the activity seems to be expected to show one content (am I wrong about this)?
Note: I already got deep linking working ( I have a website and the content map to the content in the app).
Specifically I am wondering to I dynamically change the following each time the user changes the content:
mUrl = "http://examplepetstore.com/dogs/standard-poodle";
mTitle = "Standard Poodle";
mDescription = "The Standard Poodle stands at least 18 inches at the withers";
And if yes, how about the fact that I am only supposed to make the call once (in onStart only). And again, my data is loaded from a content provider. The provider itself is loaded from the server, but that call loads everything -- as opposed to just loading a single page.
AFAIK, you should connect your GoogleApiClient once per activity only. However, you can index your dynamical content as much as you want (but better to not index content too many times), just remember to disconnect them when your activity finish. Below is what I did in my project:
HashMap<String, Action> indexedActions;
HashMap<String, Boolean> indexedStatuses;
public void startIndexing(String mTitle, String mDescription, String id) {
if (TextUtils.isEmpty(mTitle) || TextUtils.isEmpty(mDescription))
return; // dont index if there's no keyword
if (indexedActions.containsKey(id)) return; // dont try to re-indexing
if (mClient != null && mClient.isConnected()) {
Action action = getAction(mTitle, mDescription, id);
AppIndex.AppIndexApi.start(mClient, action);
indexedActions.put(id, action);
indexedStatuses.put(id, true);
LogUtils.e("indexed: " + mTitle + ", id: " + id);
} else {
LogUtils.e("Client is connect : " + mClient.isConnected());
}
}
public void endIndexing(String id) {
// dont endindex if it's not indexed
if (indexedStatuses.get(id)) {
return;
}
if (mClient != null && mClient.isConnected()) {
Action action = indexedActions.get(id);
if (action == null) return;
AppIndex.AppIndexApi.end(mClient, action);
indexedStatuses.put(id, false);
}
}
With Google Play Game Services, I'm trying to implement a CallBack such that, if there is an issue with sending a message, then I need to resolve it (as each player "passes" their bid to the next player, and all other players need to see what the player that passed bid)
I thought I would try and use the following to instantiate a RealTimeReliableMessageSentListener for that round of messages, so that I can tell if the message was sent and received by everyone:
(I add the tokenID returned by the call to an ArrayList, and then check off remove each tokenID as it comes back in to track when all messages from this round are received)
#Override
public void sendReadyToPlay() {
dLog("Sending ReadyToPlay");
// Broadcast that I'm ready, and see that they are ready
if (!mMultiplayer){
return; // playing against computer
}
// First byte in message indicates whether it's a final score or not
mMsgBuf[0] = (byte) ('R');
readyToPlayTokens.clear();
// Send to every other participant.
for (Participant p : mParticipants) {
dLog("Participant:" + p.getParticipantId());
if (p.getParticipantId().equals(mMyId)) {
continue;}
if (p.getStatus() != Participant.STATUS_JOINED){
continue;
}
readyToPlayTokens.add(mHelper.getGamesClient().sendReliableRealTimeMessage(new RealTimeReliableMessageSentListener() {
#Override
public void onRealTimeMessageSent(int statusCode, int tokenId, String recipientParticipantId){
dLog("onRealTimeMessageSent number two and size is: " + readyToPlayTokens.size());
if(readyToPlayTokens.contains(tokenId)){
readyToPlayTokens.remove(tokenId);
}
dLog("onRealTimeMessageSent number two and size is: " + readyToPlayTokens.size());
if (statusCode != GamesClient.STATUS_OK) {
mGHInterface.onRealTimeMessageReceived("RTPProblem:" + recipientParticipantId);
} else if (readyToPlayTokens.size() == 0) {
mGHInterface.beginRound();
}
}
}, mMsgBuf, mRoomId, p.getParticipantId()));
dLog("sent to:" + p.getParticipantId());
}
}
I can see the messages coming in almost every time from one device to another, so I can see that the messages are going through, BUT, the RealTimeReliableMessageSent listener is only being fired about 50-60 percent of the time, which isn't very reliable! :)
Can anyone see what I might be doing wrong to keep the listener from firing reliably?
may be it happens because you are using anonymous inner class, try to use in different way like implementing your activity RealTimeReliableMessageSentListener