Android Accessibility service interact with numerous switches at once - android

I was wondering if there is a good way to turn on/off numerous switches at once using Accessibility service.
So basically I have a view that contains a RecyclerView that has about 40 switches in it and I need a way to switch them on/off as fast as possible. Here is what I tried.
public static void scrollView(final AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null) return;
if (nodeInfo.isScrollable()) {
if (nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)) {
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
turnOffSwitches(nodeInfo);
}
}, 1000);
}
}
}
public static void turnOffSwitches(final AccessibilityNodeInfo parentView) {
if (parentView.getClassName().equals("android.support.v7.widget.RecyclerView")) {
for (int i = 0; i < parentView.getChildCount(); i++) {
final AccessibilityNodeInfo child = parentView.getChild(i);
final Boolean isLasteOne = (i == parentView.getChildCount() - 1);
child.performAction(AccessibilityNodeInfo.ACTION_CLICK);
if (isLasteOne) {
scrollView(parentView);
}
}
}
}
I even tried introducing a delay between ACTION_CLICK actions that helped a bit but did not solve my problem fully. So the issue that I am having is that not all switches get turned off. It seems like there is a limit on how fast this can be done. So my question is, is there a better way to do this?

ACTION_SCROLL_FORWARD - it's not paging just scrolling some portion of your whole matrix of elements and some visible are already switched to the state so in your next loop you are trying to click on those which were already swiched (next visible area overlaps with previous one)
either check the state on/off - if the view is not already at the desired state before changing it
or check event.getScrollX() and event.getScrollY() if action was not already performed in those bounds

Related

Show multiple TextView changes during OnClick

I am currently working on an app which uses Bluetooth, GPS and uploads data to a remote server. I have a simple button which launches a series of events and threads in order to let everything work together.
I am now adding TextView components on the screen, which show the user a more detailed process of what is happening. Is the GPS running? Is my Bluetooth device connected? Etc. This process can take up to 10 seconds, this is why I am adding some more information on what is happening on the background.
However, when I click my button, only the last change will be visible. I suppose the TextView components are rendered AFTER the Onclick?
An example:
#Override
public void onClick(View view) {
textView.setText(R.string.launching_text);
// start a thread
textView.setText(R.string.start_new_thread);
// start another thread
textView.setText(R.string.almost_there);
// start last thread
textView.setText(R.string.done);
}
Imagine this process taking about 10 seconds. It will look like the app "freezes", but the changes are not visible till after the OnClick finishes.
How can I show my information realtime, during the OnClick event? Is there perhaps a better practice? Is it possible to do some sort of way to asynchronously push TextView changes?
I’d suggest you first check this Android Performance Patterns video, to see some of the options at your disposal. I’d also advise to not perform multithreading in a lifecycle environment (e.g. Activities, Fragments) as this is just asking for trouble.
In your onClick example, R.string.done could (and most likely will) be displayed before the first thread has done its work. I’m assuming that’s not really what you want.
I have no knowledge of the problem you’re tackling, tools you’re using or the architecture you’re following, so here’s one slightly generic way to make it work. Each Thread in your onClick implementation comes with a status of sorts. You could represent this in code with a simple abstraction:
class Holder {
#StringRes int status;
Runnable runnable;
Holder(#StringRes int status, #NonNull Runnable runnable) {
this.status = status;
this.runnable = runnable;
}
}
Notice Runnable is used instead of Thread.
You’re also executing things in sequence. You could represent this in code with a simple List or a Queue, providing a fluid, expressive API, for example:
class StatusRunnableBuilder {
private final WeakReference<TextView> viewRef;
private final Queue<Holder> queue;
#StringRes private int finalStatus;
StatusRunnableBuilder(#NonNull TextView view) {
viewRef = new WeakReference<>(view);
queue = new ArrayDeque<>();
}
StatusRunnableBuilder addStep(#StringRes int status,
#NonNull Runnable runnable) {
queue.add(new Holder(status, runnable));
return this;
}
StatusRunnableBuilder withFinalStatus(#StringRes int status) {
finalStatus = status;
return this;
}
Runnable build() {
return new Runnable() {
#Override
public void run() {
for (Holder item: queue) {
updateStatus(item.status);
item.runnable.run();
}
if (finalStatus != 0) {
updateStatus(finalStatus);
}
}
};
}
private void updateStatus(#StringRes final int status) {
final TextView view = viewRef.get();
if (view != null) {
view.post(new Runnable() {
#Override
public void run() {
// As this has been posted to a queue,
// it could have been processed with some delay,
// so there is no guarantee the view is still present.
// Let's check again.
final TextView v = viewRef.get();
if (v != null) {
v.setText(status);
}
}
});
}
}
}
Then your onClick becomes something like:
#Override
public void onClick(View v) {
final Runnable runnable = new StatusRunnableBuilder(view)
.addStep(R.string.launching_text, launchingRunnable)
.addStep(R.string.almost_done, almostDoneRunnable)
.withFinalStatus(R.string.finally_done)
.build();
service.execute(runnable);
}
where service is an ExecutorService which allows you to create/shutdown on any lifecycle event, e.g.:
#Override
protected void onStart() {
super.onStart();
service = Executors.newSingleThreadExecutor();
}
#Override
protected void onStop() {
super.onStop();
service.shutdownNow();
}
You can use a Runnable with Handler. Handler will post updates to Runnable after certain intervals.
For Bluetooth connectivity you can go for Broadcast receivers as well.
You can try using an AsyncTask. It's pretty simple and it handles all the background threading as well as inter-thread communication for you. There are several considerations with it, to avoid memory leaks you can use EventBus or similar mechanics. Here's an article I found very useful:
http://simonvt.net/2014/04/17/asynctask-is-bad-and-you-should-feel-bad/

Deleting item from ListView from service caused a lot of bugs

I have ListView of Persons, ArrayAdapter to display images of this persons. There is two buttons "like" and "dislike" under photos of each person. If I press like or dislike button, this item deleted with animation from list:
protected void removeListItem(View rowView, final int positon) {
Animation out = AnimationUtils.makeOutAnimation(getActivity(), true);
rowView.startAnimation(out);
rowView.setVisibility(View.INVISIBLE);
Handler handle = new Handler();
handle.postDelayed(new Runnable() {
public void run() {
persons.remove(positon);
mMyAnimListAdapter.notifyDataSetChanged();
}
}, 500);
}
Also there is some API that generates list of person that we can get. This API makes changes to the list (status changes to one of the next value: "none", "removed", "liked, "disliked").
If status changed to "removed" I need to remove this person from list and update ListView. I do this so:
if (changedPerson.getStatus().equals("removed")) {
persons.remove(index);
mMyAnimListAdapter.notifyDataSetChanged();
notifyRemovedStatus(idOfChangedPerson);
}
But when I do this, a lot of bugs occured:
List updates only after tap.
The fragment isn't closed if last item removed from list.
ArrayIdexOfBound Exception some times occured if we press on like/dislike button.
Can you help me, why is this hepenned and how to fix it?
The solution was easy, here code without bugs:
if (changedPerson.getStatus().equals("removed")) {
persons.remove(index);
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
mMyAnimListAdapter.notifyDataSetChanged();
if (persons.size() == 0) {
getActivity().onBackPressed();
}
}});
notifyRemovedStatus(idOfChangedPerson);
}
I violated this rule "Do not access the Android UI toolkit from outside the UI thread".
This video explains working conceop:
https://www.udacity.com/course/viewer#!/c-ud853/l-1469948762/e-1530568562/m-1530568563
And this documentation was useful: http://developer.android.com/guide/components/processes-and-threads.html

Make views visible one by one; android

I'm having trouble with making views visible. As in: they do all appear, but at the same time, whereas I would like to show them with some delay in between. Currently I have the following code, which should make it more clear:
public void performExperiment (View v) {
Log.i(TAG, "Experiment has started on view: " + v);
final ArrayList<FocusPoint> permutation = new ArrayList<>(Arrays.asList(focusPoints));
Collections.shuffle(permutation);
for (FocusPoint fp: permutation) {
try {
Thread.sleep(1000);
fp.setVisibility(ImageView.VISIBLE);
//fp.invalidate();
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Sleep failed");
}
}
}
The FocusPoint-class is an extension of ImageView in Android and the method is called upon a button-click.
What needs to happen is that all the views show up in a random order on the screen, with a second between them. This code however waits for 16 seconds (the amount of views is 16) and then shows all the views at once. I tried invalidating my the view to redraw it, and I also tried to take 'fp.setVisibility(ImageView.VISIBLE)' out of the try-catch block, but both didn't work. (Of which the latter obviously doesn't work, I didn't really expect that to work, but I'm getting really desperate :P) I have been searching for hours now, and none of the StackOverflow-pages and other fora/documentation had an answer for this problem. How can I make sure that the focuspoint draws, before the loop continues to the next?
Using Thread.sleep(ms) to delay the UI thread is a very dangerous idea, and as you can see, it doesn't lead anywhere. By blocking the thread for 16 seconds, you are effectively freezing the application for that period - including any redraws and other event handling that might happen during that time. Don't ever do that.
Instead, use a Handler and its postDelayed(Runnable, ms) method to schedule visibility changes in the future. Handlers work by adding messages to the event loop, so they don't disrupt normal behavior.
Check this modified version of your code:
private static final long FP_SHOW_DELAY_MS = 1000;
private Handler handler = new Handler();
public void performExperiment (View v) {
Log.i(TAG, "Experiment has started on view: " + v);
final ArrayList<FocusPoint> permutation = new ArrayList<>(Arrays.asList(focusPoints));
Collections.shuffle(permutation);
for (int i = 0; i < permutation.size(); i++) {
final FocusPoint fp = permutation.get(i);
handler.postDelayed(new Runnable() {
#Override
public void run() {
fp.setVisibility(View.VISIBLE);
}
}, (i + 1) * FP_SHOW_DELAY_MS);
}
}

Synchronizing UI update with PostDelayed()

I'm new to Android development, and running into a synchronization issue. I made a simple matching game where you flip two cards over at a time, and try to find matches. What I want to do is flip the previous two cards over if either:
The user clicks a new card (do it immediately)
After a certain amount of time, if the user does nothing
I tried to safeguard my "flipIfFlipped" routine by marking it synchronized, and checking to be sure that the card has not already been flipped back, but I still get this double-flipping behavior (my force flip must flip it first, then the timer flips it again, or vice versa).
Here's my method on my CardView (ImageView subclass):
public synchronized void flipIfFlipped() {
if (this.flipped) {
this.flip();
}
}
Here's my force flip at the top of my onItemClickListener, if we clicked a card while two are still up:
CardView lastCard = game.getLastCard();
if (null != lastCard){
lastCard.flipIfFlipped();
}
CardView secondLastCard = game.getSecondLastCard();
if(null != secondLastCard) {
secondLastCard.flipIfFlipped();
}
And here's my attempted delayed flip, later, if we just got mismatched cards:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
clickedImage.flipIfFlipped();
lastCard.flipIfFlipped();
}
}, 3000L);
Again, I'm a newbie, so I'm sure I'm probably missing something basic.

Highlight buttons one at a time

I am making an Android game modeled after the old Simon game. It is a little different in the layout as it is using a 3x3 layout of buttons. I am trying to get the buttons to light up one at a time inside the loop that randomly selects a button. The trouble I have is that all of the buttons light up at once and only the last (or first, not sure) changes back to the original color. I have tried very thoroughly to find an appropriate answer to my situation but have had no luck here or elsewhere. The button id(s) are in the butts[]. butts[0] is button 1, butts[2] ... Below is my attempt.
public void play()
{
for(int x = 0; x <= numButtons; ++x)
{
spot = randomGenerator.nextInt(9);
playMe[x] = spot;
//butts[spot].setBackgroundColor(Color.parseColor("#540115"));
handler.postDelayed(new Runna(spot), (x+1)*1000);
}
}
class Runna implements Runnable
{
public Runna(int j2)
{
butts[j2].setBackgroundColor(Color.parseColor("#540115"));
}
public void run()
{
butts[spot].setBackgroundColor(Color.LTGRAY);
}
}
I think it has to do with the value of spot. It is global to the functions and you change it every time. It runs, but at the end there is still only one spot and EVERY runnable is trying to change that same spot.
Perhaps save spot in your runnable?
class Runna implements Runnable
{
int s;
public Runna(int j2)
{
s = j2;
butts[s].setBackgroundColor(Color.parseColor("#540115"));
}
public void run()
{
butts[s].setBackgroundColor(Color.LTGRAY);
}
}
Have you tried to invalidate the button each time?
public Runna(int j2)
{
butts[j2].setBackgroundColor(Color.parseColor("#540115"));
butts[j2].invalidate();
}

Categories

Resources