I've created an AccessibilityService in Android where I want to display an AlertDialog after focusing on an EditText on the page (Note: The EditText could be from another app, like a login screen, so I won't always know the ID). When you click on a 'confirm' button within the AlertDialog it populates that EditText with text.
I have all of the steps complete except for the last part...I cannot figure out how to populate the EditText with text. I'm guessing there is a way to cast a findViewByID() method somewhere, but I don't know how to find the ID of the EditText (see above, the EditText could be from another app). See code below, am I way off? The code below always errors out telling me about a sealed instance problem (Cannot perform this action on a not sealed instance.).
public void onAccessibilityEvent(final AccessibilityEvent event) {
final AccessibilityNodeInfo source = event.getSource();
if ((event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED || event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) && CLASS_NAME_EDIT_TEXT.equals(event.getClassName())) {
AlertDialog.Builder mSuspendDialog = new AlertDialog.Builder(getApplicationContext())
.setTitle(R.string.str_have_password_question)
.setPositiveButton(R.string.str_decision_use_password, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
if (event.getSource() != null & event.getClassName().equals("android.widget.EditView")) {
Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo
.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "newtexttopopulateedittext");
event.getSource().performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
}
}
})
.setNegativeButton(R.string.str_decision_close, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User cancelled the dialog
}
});
AlertDialog alert11 = mSuspendDialog.create();
// Ensure we can show the dialog from this service.
alert11.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
alert11.show();
1) When any activity starts, create a List(or whatever) of ID's of all the EditText's on the screen.
2) While creating a list (or later on) iterate through the list again and check if a value is available for that view in your SQLite database.
3) If a value is available, use ACTION_PASTE to inject text in that EditText.
ArrayList<AccessibilityNodeInfo> inputViewsList = new ArrayList<AccessibilityNodeInfo>();
AccessibilityNodeInfo rootNode = getRootInActiveWindow();
refreshChildViewsList(rootNode);
for(AccessibilityNodeInfo mNode : inputViewsList){
String viewId = mNode.getViewIdResourceName();
Cursor cr = db.rawQuery(-----your database parameters----);
if(cr.moveToFirst()){
//this means you have a value for that ET
ClipboardManager clipboardManager = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
ClipData clipData =ClipData.newPlainText("MyAppName", cr.getString(--Your column number--));
clipboardManager.setPrimaryClip(clipData);
mNode.performAction(AccessibilityNodeInfo.ACTION_PASTE);
}
}
Database has to be used so that you can remember the values permanently. The ID which i'm getting above is a fully qualified ID with App package name and view ID. So, it remains unique system-wide.
Now about refreshChildViews() method:
private void refreshChildViews(AccessibilityNodeInfo rootNode){
int childCount = rootNode.getChildCount();
for(int i=0; i<childCount ; i++){
AccessibilityNodeInfo tmpNode = rootNode.getChildAt(i);
int subChildCount = tmpNode.getChildCount();
if(subChildCount==0){
if(tmpNode.getClassName().toString().contentEquals("android.widget.EditText"){
inputViewsList.add(tmpNode);
}
return;
}
if(subChildCount>0){
refreshChildViews(tmpNode);
}
}
}
Don't forget to clear the inputViewsList on TYPE_WINDOW_STATE_CHANGED event!
Let me know if you need anything else.
Related
As a person who's new to Android programming, I have finally completed most of the functions I want my app to have except for the save/load system. (Although I still have to design the interface.)
Before I proceed, here is my code for the save button:
saveList.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("Save List");
final EditText saveName = new EditText(MainActivity.this);
saveName.setInputType(InputType.TYPE_CLASS_TEXT);
builder.setView(saveName);
builder.setPositiveButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.setNegativeButton("Save", new DialogInterface.OnClickListener()
#Override
public void onClick(DialogInterface dialog, int which) {
m_Text = saveName.getText().toString();
}
});
builder.show();
}
Now, what I want to learn (know) is the method/code on how to save the values from my Array:
ArrayList<String> values = new ArrayList<>();
int idList[] = new int[]{R.id.textBox1, R.id.textBox2, R.id.textBox3,
R.id.textBox4, R.id.textBox5, R.id.textBox6};
for (int id : idList) {
String stringList = ((EditText) findViewById(id)).getText().toString();
if (!TextUtils.isEmpty(stringList)) {
values.add(stringList);
}
}
myItems = values.toArray(new String[values.size()]);
And load those values into their respective places
(e.g myItems[0] should be placed in textBox1, myItems[1] into textBox2 and so on...).
Any help would be appreciated. Thank you very much.
EDIT: My mistake about the question being misleading. I want to know how to save the values into a text file or an android given file and then load it for later use.
If you want to restore back all the strings back into the EditTexts you need to maintain the mapping between the content and the EditText. In your case above, you are storing only the strings, that are not empty. So suppose your first EditText is empty, you won't store it in the values. But if you do that, your entire ordering will be messed up. You won't know which string to put where. The quickest workaround for this, is to store all the EditTexts string, even though if its empty. So your code will change to
for (int id : idList) {
String stringList = ((EditText) findViewById(id)).getText().toString();
// Store everything in values
values.add(stringList);
}
myItems = values.toArray(new String[values.size()]);
And then to load everything back, you can do something like
for (int i = 0; i < idList.length; i++) {
((EditText) findViewById(idList[i])).setText(myItems[i]);
}
I have a small random number spinner that when you click gives a random number. I am having two problems. The first is when the main activity loads it displays a random number on the screen without the random number spinner being clicked. I am unsure what to set to false to keep it from opening with the main activity. The second problem is that when you select an option from the spinner it does not clear. Meaning that If you click on option D6 or D20 then you can not click on the same option again until selecting the other option first. Essentially the selection does not clear out of memory after the random number is selected. Here is the random number code
public void onItemSelected(AdapterView<?> parent, View view, int pos,
long id) {
Random rand = new Random();
int roll = 0;
boolean firstRun = false;
// An item was selected.
if (!firstRun)
{
if (spinner1.getSelectedItemPosition()==0)
{
roll = rand.nextInt(6)+1;
}
else
{
roll = rand.nextInt(20)+1;
}
}
else
{ firstRun = false; }
// Put the result into a string.
String text = "You rolled a " + roll;
// Build a dialog box and with the result string and a single button
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(text).setCancelable(false)
.setPositiveButton("OK", new
DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id)
{
// do things when the user clicks ok.
}
});
AlertDialog alert = builder.create();
// Show the dialog box.
alert.show();
}
...when the main activity loads it displays a random number on the screen without the random number spinner being clicked.
This is because onItemSelected() is called when the Activity is first created. To avoid running this code simply create a member variable (declared outside of a method, preferably before onCreate() for readability) like a boolean. And check that. For example
public void onItemSelected(AdapterView<?> parent, View view, int pos,
long id)
{
if (!firstRun) // where firstRun is the boolean variable you create
// and set to true
{
// run your code
}
else
{ firstRun = false; }
}
The second problem is that when you select an option from the spinner it does not clear.
I'm not exactly sure what you mean by this but you could set an empty value ("") for your first position then after each call to onItemSelected() call setSelection(0)
I am having trouble with a ListView based on a MatrixCursor resetting values on keyboard minimize and orientation change.
The matrix cursor is filled with data from a SQL database. This is done because I want the original database columns to be rows in the listview:
public void fillList(boolean rewrite, int rewrite pos, int) {
myCols = getColArray();
SQLiteDatabase myDB = SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.OPEN_READONLY);
myCursor = myDB.query("Scoring", statCols, null, null, null, null, null);
String[] menuCols = new String[] { "_id","Item","Value"};
int []to = new int[]{R.id.listitem, R.id.listitem_value};
mc = new MatrixCursor(menuCols);
for (int i = 0; i < (mChoices.size()); i++) {
statCursor.moveToPosition(draftId);
String item = mChoices.get(i);
String valueString = "";
int valueCol = statCursor.getColumnIndex(item);
int value = statCursor.getInt(valueCol);
valueString = (String.valueOf(value));
mc.addRow(new Object[] {
i,
item,
valueString
});
}
This fillList() method is called in my onCreate. This cursor is then used to populate the ListView by extending SimpleCursorAdapter. My goal for the fragement is to allow the user to click on one of the list items, causing Dialog with an EditText line to appear:
#Override
public void onListItemClick(ListView l, final View v, int position, long id) {
final TextView valueL = (TextView) v.findViewById(R.id.listitem_value);
String valueString = (String) valueL.getText();
int value = 100;
try {
value = Integer.parseInt(valueString);
} catch(NumberFormatException nfe) {
}
//Create dialog layout
View view = getActivity().getLayoutInflater().inflate(R.layout.numberpick_popup, null);
final EditText mInputText = (EditText) view.findViewById(R.id.myText);
//Create Dialog
AlertDialog.Builder builder = new AlertDialog.Builder(this.getActivity());
builder.setTitle(title);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
valueL.setText(mInputText.getText().toString());
return;
}
});
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
return;
}
});
builder.setView (view);
final AlertDialog dialog = builder.create ();
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
dialog.show();
}
The problem I am having is when the fragment layout regenerates on keyboard hidden or orientation change, causing fillData to be called and overwriting any of the values I had updated in the list. This happens if I choose OK on the dialog without first minimizing the keyboard, or on any orientation change.
My thinking is that there should be some way to overwrite the values in the MatrixCursor when new data is entered from the dialog. However I don't know if this is possible or if the cursor would even survive the state changes.
Any help would be greatly appreciated.
I worked around this by creating a HashMap of key value pairs in my list adaptor and adding the changed values there. So in bindView() I would check if the key existed there for the row being initialized and if so then use those values instead of from the cursor.
I simply did
HashMap<Long,String>
since I only needed one value that could change but you could easily create a custom class and make yours
HashMap<Long,CustomClass>
so you could change lots of values.
So when changing the values initially just remember to update that HashMap with the new values so on redraw of your List the correct values are shown. This works well because by nature a MatrixCursor alone is usually pretty small. This wouldn't be smart on a really large list because the HashMap would grow out of control and take up too much memory. Let me know if you have any questions.
I would like to check a number of Spinner controls in the Onclick event of a few buttons (if the user moves away from the Activity), and if one or more were not filled out, display a warning message with 2 options to the user:
#Override
public void onClick(View v) {
int i1 = spinner1.getSelectedItemPosition();
(..)
if ((i1 == 0) | (i2 == 0) | (i3 == 0) | (i4 == 0) | (i5 == 0)) {
AlertDialog.Builder alertbox = new AlertDialog.Builder(this);
alertbox.setMessage("Answers missing, what do you want to do?");
final CharSequence[] items = {
"Oops.. let me fix that…",
"I want to leave the app now" };
alertbox.setTitle("Pick an item");
alertbox.setItems(items, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
if (item == 1) {
finish();
// should lead back to the spinner controls on the page
}else if (item == 2){
finish();
android.os.Process.killProcess(android.os.Process
.myPid());
}
}
}).create();
alertbox.show();
However, I just see an empty box with the title, but the message set with setMessage() is not displayed, and I don't get any list items for selection - the box is empty! Any idea what I can do to fix that?
I have been having the same issue, and I found that if I remove the .setMessage line, then the list appears with the title. Not sure why this is though. If you use the .setpositive button, .negative, etc, then you can have a message. I also noticed that google examples all don't use .setMessage with an item list. They only use .setTitle, which doesn't give you that much space.
So, while this doesn't help you if you want a long message, it will get your list visible if that is your main goal.
I've created an array of 5 clickable textviews using a loop, have set their parameters (size, colour, background image, to be clickable etc) and have set an onClickListener and the array is called "myArrayofTVs". Their ids have been set using the loop int (i). I have another predefined array that hold text string, and other textviews are present on the layout. Later on in the onClick method, and as all the buttons/clickable textviews do something very similar, I'd like to be able to do something like:
#Override
public void onClick(View v) {
if(v == myArrayofTVs[i]) { //using 'i' here doesn't seem to work
tv1.setText(myArray2[i]);
tv2.setText(myArray2[i+1];}
etc
etc}
I've tried various differnt ways such as using switch case statements (don't really want to use these as there will be a lot of repeated code and I'll have to add a new case statement each time I want to add new textview/buttons in the future). Is there anyway of using one statement that will handle all the buttons/clickable textviews based on the variable id given or will I have to use a separate case/tag/id statement for each one?
Many thanks in advance!
Add the views to your ViewGroup and use getChildAt(int index) and getChildCount() to create a loop. You can loop all children/views in the viewgroup and you could check with
if(child instanceof TextView)
if they are of the correct type. Then you could cast the views back to a TextView/Button/View and do the thing you want to do.
But it sounds like you want a list of something. So i would suggest using a ListView with a adapter behind it.
I really think you should use the id provided by Android instead of trying to compare objects. The reason your code wouldn't work, if it had a sufficient for loop around it, is somewhat mysterious, but I would try to parallel the switch statements you see in examples as much as possible by comparing ID's and not objects.
for( int i = 0; i < myArrayofTvs.length; i++ )
if(v.getId() == myArrayofTVs[i].getId()) {
tv1.setText(myArray2[i]);
tv2.setText(myArray2[i+1];
}
}
Also obviously you'll want to avoid an array out of bounds error in that second inner statement.
What I did was programmatically inflate my custom layout and used an onClickListener on that button from the custom layout inflated. Then to interact with a specific item I got the parent view of the view being clicked eg. your button and then used that view to change attributes of the view. This is a snippet of my code. The onClick of the alertDialog is where I go about changing values of the newly inflated view.
// if an edit button of numbers row is clicked that number will be edited
if (view.getId() == R.id.NumberRowEditButton)
{
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.setTitle("Contact edit");
alert.setMessage("Edit Number");
// Set an EditText view to get user input
final EditText input = new EditText(this);
input.setSingleLine();
alert.setView(input);
alert.setPositiveButton("Ok", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichButton)
{
// get input
Editable value = input.getText();
if(value.length() > 4){
View tempView = (View) view.getParent();
TextView tempTV = (TextView) tempView.findViewById(R.id.numberRowTextView);
String number = tempTV.getText().toString();
tempTV.setText(value.toString());
}
else
{
// ...warn user to make number longer
final Toast msgs = Toast.makeText(ContactEdit.this, "Number must be over 4 digits.", Toast.LENGTH_SHORT);
msgs.setGravity(Gravity.CENTER, msgs.getXOffset() / 2, msgs.getYOffset() / 2);
msgs.show();
}
}
});
alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichButton)
{
// cancel the dialog
dialog.cancel();
}
});
alert.show();
}
Hopefully this might help you.