What I am trying to accomplish here is to make a Cursor that could be used in an android ListView. I'm reading values directly from multiple files and have to feed them to the cursor. I tried to use MatrixCursor but I can't get it to work with arrays. I have posted my attempt at it so far below and I'm open to all new suggestions. Is there a simpler way to do this?
static MatrixCursor getnameList() {
ArrayList<String> fsitem = getfsiList();
MatrixCursor cursor;
cursor = null;
for (int i = 0; i < fsitem.size(); i++) {
try {
File root = new File(Environment.getExternalStorageDirectory()
.getName() + "/" + fsitem.get(i));
if (root.canRead()) {
File namefile = new File(root, ".name");
FileReader namereader = new FileReader(namefile);
BufferedReader in = new BufferedReader(namereader);
String name = in.readLine();
String id = in.readLine();
String info = in.readLine();
String[] fsii = new String[3];
fsii[0]= name;
fsii[1]= id;
fsii[2]= info;
cursor.addRow(fsii); //crashes here on running.
}
} catch (IOException e) {
Log.e("NameManager.java : ", ("Error!! Not Writable!!"
+ Environment.getExternalStorageDirectory().getName()
+ "/" + fsitem.get(i)));
}
}
This code compiles but crashes at cursor.addRow(fsii);:
with 02-24 21:16:49.589: E/AndroidRuntime(3895): at com.manager.abcd.r1223.NameManager.getnameList(NameManager.java:81).
I'm thinking this is a problem with MartixCursor not supporting arrays, but I might be wrong. Any ideas?
If this is all the code then it is normal because you try to add a row on a null cursor(you never initialize cursor) and probably get a NullPointerException.
Initialize the MatrixCursor before you enter in the for loop:
String[] columnNames = {"col1", "col2", "col3"};
MatrixCursor cursor = new MatrixCursor(columnNames);
Check the docs.
Related
How to read filenames from the download directory on android 10 and higher?
After filtering the names I want to let users select from this list to open
the file.
On Android versions 8 (API 28) the FILE api is quite simple to use.
Filenames are simple to read with the method DirListOld. With these names
I can read the content of the files.
I tried to make a method to do the same on Android 10 (API 30) and higher.
But documentation is not very clear. I did some experimenting with
MediaStore methods, but I could not get the filenames only got directory
names on external storage.
How to filter the results is not very well documented and examples of the
MediaStore.Downlaods are totally absent.
My experiment is shown in method DirListNew.
Also I had to ask for a permission for MANAGE_EXTERNAL_STORAGE. Without this
permission even DirListNew results in an empty string. As I read in several
comments Google-Play is not generous in giving this permission. Why not
special permission for only downloaded files. I don't have to read all external
files.
I don't understand why Google-Android developers made such a mess for retrieving
simple downloaded files.
public String DirListOld()
{
String sName;
File oDownloadDir;
String sDownloadDir;
StringBuilder dirContent = new StringBuilder();
oDownloadDir = this.getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
try {
sDownloadDir = oDownloadDir.getName();
if (!sDownloadDir.equals("") )
{
for (File f : Objects.requireNonNull(oDownloadDir.listFiles()))
{
if (f.isFile())
{
sName = f.getName();
dirContent.append(sName);
dirContent.append("\n");
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return dirContent.toString();
} // DirListOld
#RequiresApi(api = Build.VERSION_CODES.Q)
public String DirListNew()
{
EditText editText = findViewById(R.id.editTextMultiLine2);
StringBuilder dirContent = new StringBuilder();
String[] projection = new String[] {
MediaStore.Downloads.DATA
};
String selection = null;
String[] selectionArgs = null;
String sortOrder = null;
Cursor cursor = getApplicationContext().getContentResolver().query(
MediaStore.Files.getContentUri("external"),
projection,
selection,
selectionArgs,
sortOrder
);
if (cursor != null) {
cursor.moveToFirst();
//iterate over rows
for (int i = 0; i < cursor.getCount(); i++) {
//iterate over the columns
for(int j = 0; j < cursor.getColumnNames().length; j++){
//append the column value to the string builder and delimit by \n
dirContent.append(cursor.getString(j));
dirContent.append("\n");
}
//add a new line carriage return
dirContent.append("\n");
//move to the next row
cursor.moveToNext();
}
//close the cursor
cursor.close();
}
return dirContent.toString();
} // DirListNew
I am developing an android app which backs up and restores the messages/conversations from device. It backup the messages, export file in the form of xml, and then later restore it. The only problem I am facing is the date/times of conversations. It is changed to current time at the time of restoration, but when I open any conversation, there time is correct. Have a look at photos.
Before backup:
After backup:
Code I am using for backup:
Uri uri = Uri.parse("content://sms/inbox");
//Uri uri = Uri.parse("content://mms-sms/conversations/");
ContentResolver contentResolver = getContentResolver();
final String[] projection = new String[]{"*"};
Cursor SMSL = contentResolver.query(Telephony.Sms.Inbox.CONTENT_URI, projection, null, null, null);
int msgscount = SMSL.getCount();
if (msgscount>0) {
msgs = new String[SMSL.getCount()][5];
int i = 0;
while (SMSL.moveToNext()) {
address = SMSL.getString(SMSL.getColumnIndex("address"));
body = SMSL.getString(SMSL.getColumnIndex("body"));
read = SMSL.getString(SMSL.getColumnIndex("read"));
date = SMSL.getString(SMSL.getColumnIndex("date"));
type = SMSL.getString(SMSL.getColumnIndex("type"));
msgs[i][0] = address;
msgs[i][1] = body;
msgs[i][2] = date;
msgs[i][3] = read;
msgs[i][4] = type;
Log.i("Date: ", String.valueOf(SMSL.getLong(SMSL.getColumnIndex("date"))));
i++;
}
SMSL.close();
}else{
msgs = new String[0][0];
Toast.makeText(getApplicationContext(),"No messages found!",Toast.LENGTH_LONG).show();
}
Code for restoring:
ContentResolver contentResolver = getContentResolver();
Uri uri = Uri.parse("content://sms/inbox");
//Uri uri = Uri.parse("content://mms-sms/conversations/");
ContentValues values = new ContentValues();
for (int i = 0; i < readMsgsFromFile.length; i++) {
values.put("address",readMsgsFromFile[i][0]);
values.put("body",readMsgsFromFile[i][1]);
values.put("date",readMsgsFromFile[i][2]);
values.put("read",readMsgsFromFile[i][3]);
values.put("type",readMsgsFromFile[i][4]);
contentResolver.insert(Telephony.Sms.Inbox.CONTENT_URI, values);
Log.i("Restoring: ",readMsgsFromFile[i][2]);
}
Thanks Mike M. I did find a solution and you are right, the conversation table is updated whenever a new message is received or sent by a user and the time of conversation is same as that message's (whether received or sent) time. But in case of writing messages through contentresolver query it does not work and the conversation time is current time at the time of writing. So what I did is add a temporary message in all of the conversations, right after messages are restored. And after that delete all the temporary messages, this will update the conversations time to last message time.
HashSet hs = new HashSet();
hs.addAll(list);
//list is the ArrayList<String> which contains the addressess of all the messages and
//through hashset we remove all the duplicates to get only the addressess once and hence we know the number of conversations and their addressess.
list.clear();
list.addAll(hs);
//Add some dummy message to each conversation
ContentValues values2 = new ContentValues();
for (int i = 0; i < list.size(); i++) {
values2.put("address",list.get(i));
values2.put("date_sent",readMsgsFromFile[0][1]);
values2.put("date",readMsgsFromFile[0][2]);
values2.put("type",readMsgsFromFile[0][3]);
values2.put("body","temp"); //this should be more unique
values2.put("read",readMsgsFromFile[0][5]);
values2.put("service_center","01010101");
contentResolver.insert(Telephony.Sms.CONTENT_URI, values2);
}
//Now deleting that message with body 'temp' from each conversation
Cursor c = contentResolver.query(Telephony.Sms.CONTENT_URI,null,null,null,null);
while (c.moveToNext()){
String body = c.getString(c.getColumnIndex("body"));
String mid = c.getString(0);
if (body.equals("temp")){
Log.i("Deleting ",mid);
getContentResolver().delete(Uri.parse(Telephony.Sms.CONTENT_URI+"/"+mid),null,null);
}
}
c.close();
This word 'temp' could be and should be more unique so that it is not mixed with actual message.
Following This Retrieving a List of Contacts Tutorial in the android developers site, I managed to implement contacts search functionality. Here is my code so far
private void retrieveContactRecord(String phoneNo) {
try {
Log.e("Info", "Input: " + phoneNo);
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
Uri.encode(phoneNo));
String[] projection = new String[]{ContactsContract.PhoneLookup._ID, ContactsContract.PhoneLookup.DISPLAY_NAME};
String sortOrder = ContactsContract.PhoneLookup.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
ContentResolver cr = getContentResolver();
if (cr != null) {
Cursor resultCur = cr.query(uri, projection, null, null, sortOrder);
if (resultCur != null) {
while (resultCur.moveToNext()) {
String contactId = resultCur.getString(resultCur.getColumnIndex(ContactsContract.PhoneLookup._ID));
String contactName = resultCur.getString(resultCur.getColumnIndexOrThrow(ContactsContract.PhoneLookup.DISPLAY_NAME));
Log.e("Info", "Contact Id : " + contactId);
Log.e("Info", "Contact Display Name : " + contactName);
break;
}
resultCur.close();
}
}
} catch (Exception sfg) {
Log.e("Error", "Error in loadContactRecord : " + sfg.toString());
}
}
Here is the catch, this code works pretty great, but I need to implement a smart search here. I want 26268 to match Amanu as well as 094 526 2684. I believe it is called T9 dictionary.
I tried looking at other projects for clue, but I couldn't find anything. Any pointers would be appreciated!
T9 search can be implemented using trie data structure. You can see an example here - Trie dict.
After implementing something similar you will be able to convert your search input into its possible T9 decoded variant and compare if it matches with name.
Dump all contacts to a HashSet
Set<String> contacts = new HashSet<String>();
Then search:
List<List<String>> results = new ArrayList<List<String>>();
// start the search, pass empty stack to represent words found so far
search(input, dictionary, new Stack<String>(), results);
Search method (from #WhiteFang34)
public static void search(String input, Set<String> contacts,
Stack<String> words, List<List<String>> results) {
for (int i = 0; i < input.length(); i++) {
// take the first i characters of the input and see if it is a word
String substring = input.substring(0, i + 1);
if (contacts.contains(substring)) {
// the beginning of the input matches a word, store on stack
words.push(substring);
if (i == input.length() - 1) {
// there's no input left, copy the words stack to results
results.add(new ArrayList<String>(words));
} else {
// there's more input left, search the remaining part
search(input.substring(i + 1), contacts, words, results);
}
// pop the matched word back off so we can move onto the next i
words.pop();
}
}
}
The ContentProvider for contacts doesn't support it. So what I did was to dump all of the contacts in a List then use a RegEx to match for the name.
public static String[] values = new String[]{" 0", "1", "ABC2", "DEF3", "GHI4", "JKL5", "MNO6", "PQRS7", "TUV8", "WXYZ9"};
/**
* Get the possible pattern
* You'll get something like ["2ABC","4GHI"] for input "14"
*/
public static List<String> possibleValues(String in) {
if (in.length() >= 1) {
List<String> p = possibleValues(in.substring(1));
String s = "" + in.charAt(0);
if (s.matches("[0-9]")) {
int n = Integer.parseInt(s);
p.add(0, values[n]);
} else {
// It is a character, use it as it is
p.add(s);
}
return p;
}
return new ArrayList<>();
}
.... Then compile the pattern. I used (?i) to make it case insensitive
List<String> values = Utils.possibleValues(query);
StringBuilder sb = new StringBuilder();
for (String value : values) {
sb.append("[");
sb.append(value);
sb.append("]");
if (values.get(values.size() - 1) != value) {
sb.append("\\s*");
}
}
Log.e("Utils", "Pattern = " + sb.toString());
Pattern queryPattern = Pattern.compile("(?i)(" + sb.toString() + ")");
You'll know what to do after this.
Here I am reading file word by word and manipulating List view with these word. Problem here is First name and Last name are appearing in different rows. e.g. Name = "John Clerk" then I am getting "John" in first row and "Clerk" in second row of List view. They must be in single row and so forth for other data. What should I make changes to work it properly? My code...
String myData = "";
String strLine;
String listName = "" ;
FileOutputStream fos;
FileInputStream fstream;
DataInputStream in;
String[] SavedFiles;
BufferedReader br;
public void readFile(String file) throws IOException
{
fstream = openFileInput(file);
Scanner scanFile = new Scanner(new DataInputStream(fstream));
ArrayList<String> words = new ArrayList<String>();
String theWord, theWord1, theWord2;
while (scanFile.hasNext())
{
theWord = scanFile.next();
words.add(theWord);
}
Toast.makeText(getBaseContext(), "" + size, 1000).show();
adapterFriends = new ArrayAdapter<String>(getBaseContext(), R.layout.text, words);
lvFinal.setAdapter(adapterFriends);
adapterFriends.notifyDataSetChanged();
}
Try to use nextLine() instead of next(), as it should return every string between \n chars.
http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Scanner.html#nextLine()
hope that helps
If I understand correctly what you need, try this:
while (scanFile.hasNext())
{
String name = scanFile.next();
if (scanFile.hasNext())
{
name = String.format("%s %s", name, scanFile.next());
}
words.add(name);
}
I have a List View that Displays songs in alphebetical order being populated by this method
public void updatelist(){
Cursor cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null,null);
int j =0;
if(cursor.moveToFirst()){
do{
int ALBUM_ID = cursor.getInt((cursor.getColumnIndex(MediaStore.Audio.AlbumColumns.ALBUM_ID)));
int pathcolumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);
String path1 = cursor.getString(pathcolumn);
String album_url = null;
Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
Uri uri = ContentUris.withAppendedId(sArtworkUri, ALBUM_ID);
album_url = uri.toString();
ContentResolver res = this.getContentResolver();
// Album
String album_name = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.AlbumColumns.ALBUM));
String year = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.AudioColumns.YEAR));
// String year = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.AlbumColumns.NUMBER_OF_SONGS));
// artist
String artist_name = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.ArtistColumns.ARTIST));
// display name
String DisplayName = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME));
//title
String Title = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.TITLE));
songtitle.add(Title);
Collections.sort(songtitle);
artistname.add(songtitle.indexOf(Title), artist_name);
albumname.add(songtitle.indexOf(Title), album_name);
path.add(songtitle.indexOf(Title),path1);
albumartwork.add(songtitle.indexOf(Title),album_url);
j++;
}while(cursor.moveToNext());
}
Collections.sort(songtitle);
adapter = new ArrayAdapter<String>(this,R.layout.song,songtitle);
setListAdapter(adapter);
}
My Question is i want to insert Dividers whenever the first letter of the SongName Changes.
I have this method to get the first letter of the songname if it is different than the previous..
private void alphebetdividers(ArrayList<String> songtitle2) {
String newString = null;
// TODO Auto-generated method stub
ArrayList<Character> letters = new ArrayList<Character>();
int i = 0;
int j = 0;
while( j < songtitle.size()-1){
if(songtitle2.get(i).charAt(0) == songtitle2.get(i+1).charAt(0)){
Toast.makeText(getApplication(), songtitle2.get(i).charAt(0) + "== " + songtitle2.get(i+1).charAt(0), Toast.LENGTH_LONG).show();
}else{
// Display Char with TextView
String songName = songtitle2.get(i);
newString = songName.substring(0, 1);
}
j++;
i++;
}
How would i display this in the list view at the appropriate spots. Thank you and i will give u a good rating if u know the answer.
You probably want an Adapter that implements SectionIndexer, specifically AlphabetIndexer. See this or this.
You really should be using an Adapter... there are libraries that do all of this work for you, you shouldn't have to do it yourself in a massive loop!
Here is some sample code to help you get started... note that it is a little out-dated in that it makes use of some deprecated methods (such as managedQuery, etc.). If you wanted to be entirely 100% correct you would want to make use of the LoaderManager.LoaderCallbacks<Cursor interface introduced in the Honeycomb release, but it's a good start.
To sort the displayed views, you can make use of the sortOrder argument in the ContentProvider's query method.