I've seen plenty of examples of how to set a default ringtone, but what I'm more interested in is being able populate a drop down box list filled with the available ringtones on the phone. So the list that people see when they change their ringtone in the android settings, I want to be able to list all of those.
The closest thing I've found is here, but again this is just for setting the default ringtone. Any ideas anyone? It can be in or out of ringtonemanager.
This will return you the title and uri of all the ringtones available. Do with them what you wish!
public Map<String, String> getNotifications() {
RingtoneManager manager = new RingtoneManager(this);
manager.setType(RingtoneManager.TYPE_RINGTONE);
Cursor cursor = manager.getCursor();
Map<String, String> list = new HashMap<>();
while (cursor.moveToNext()) {
String notificationTitle = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
String notificationUri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX) + "/" + cursor.getString(RingtoneManager.ID_COLUMN_INDEX);
list.put(notificationTitle, notificationUri);
}
return list;
}
RingtoneManager is what you are looking for. You just need to use setType to set TYPE_RINGTONE and then iterate over the Cursor provided by getCursor.
This is a working example of an hypothetical method that returns an array of URIs, with the only slight difference that it's looking for alarms instead of ringtones:
RingtoneManager ringtoneMgr = new RingtoneManager(this);
ringtoneMgr.setType(RingtoneManager.TYPE_ALARM);
Cursor alarmsCursor = ringtoneMgr.getCursor();
int alarmsCount = alarmsCursor.getCount();
if (alarmsCount == 0 && !alarmsCursor.moveToFirst()) {
return null;
}
Uri[] alarms = new Uri[alarmsCount];
while(!alarmsCursor.isAfterLast() && alarmsCursor.moveToNext()) {
int currentPosition = alarmsCursor.getPosition();
alarms[currentPosition] = ringtoneMgr.getRingtoneUri(currentPosition);
}
alarmsCursor.close();
return alarms;
Related
I am trying to use DocumentsContract to traverse a directory recursively using the following method.
void traverseDirectoryEntries(Uri rootUri) {
ContentResolver contentResolver = activityMain.getContentResolver();
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(
rootUri, DocumentsContract.getTreeDocumentId(rootUri));
List<Uri> dirNodes = new LinkedList<>();
dirNodes.add(childrenUri);
while (!dirNodes.isEmpty()) {
childrenUri = dirNodes.remove(0); // get the item from top
try (Cursor cursor = contentResolver.query(childrenUri, new String[]{
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_MIME_TYPE},
null, null, null)) {
if (cursor != null) {
while (cursor.moveToNext()) {
final String docId = cursor.getString(0);
final String name = cursor.getString(1);
final String mime = cursor.getString(2);
if (isDirectory(mime)) {
final Uri newNode = DocumentsContract.buildChildDocumentsUriUsingTree(
rootUri, docId);
traverseDirectoryEntries(newNode);
}
}
}
}
}
}
// Util method to check if the mime type is a directory
private static boolean isDirectory(String mimeType) {
return DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType);
}
Unfortunately, this does not work. It goes through the same root directory over and over despite the directory passed in being a child of the root. The line final Uri newNode = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, docId); returns a Uri with the child directory attached at the end. That works fine. The problem is the follwing line: Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, DocumentsContract.getTreeDocumentId(rootUri)); where rootUri is the newNode. I get the same childrenUri; the one I got from rootUri. Is this a bug? Did I make a mistake? Is the docId wrong? I tried getDocumentId(rootUri), but that throws an error because the id it returns is not a tree. It is almost like getTreeDocumentId(rootUri) is returning what getRootId(rootUri) should be returning. Is there a way to fix this?
The rootUri was obtained via ACTION_OPEN_DOCUMENT_TREE. I am running this on Android Pie. I left some code out where I am building a tree structure from the data.
You are abusing this well known void traverseDirectoryEntries(Uri rootUri) function.
You think it is recursive but it is not.
Remove the recursive call and instead add 'newNode' to the list.
// traverseDirectoryEntries(newNode);
dirNodes.add(newNode);
Then you make use of the list.
Then original code is restored.
Who changed it without telling so?
Of course you can adapt this function to make it recursiv.
Nice exercise ;-).
I am trying to permanently remove a Android Contact Group and have used the Sync parameter and it always appears the record is simply marked as deleted and not physically removed. Can anyone explain how/when, if ever, the Contract group row is deleted permanently or show a snippet of code demonstrating how to do this? The records I am trying to remove are ones that I added, so they are not Read-Only.
Linked back to https://stackoverflow.com/a/21376905/5398898
My Delete Code:
private void RemoveGroup()
{
TextView tv = (TextView) this.findViewById(R.id.helloworld);
int[] startId = {10};//{6, 7, 8, 9, 10, 11};
String groupName = "My New Contacts";
Uri mUri = ContactsContract.Groups.CONTENT_URI;
mUri.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();
for (int n = 0; n < startId.length; n++) {
groupCount = startId[n];
ContentValues values = new ContentValues();
values.put(ContactsContract.Groups._ID, groupCount);
try {
getContentResolver().delete(mUri, values.toString(),null);
} catch (Exception ex) {
tv.setText(ex.getMessage());
}
}
}
Result when reading the groups:
Image can be found here http://i.stack.imgur.com/5OOfc.png
You are building the correct Uri but not using it, try like this
Uri mUri = ContactsContract.Groups.CONTENT_URI;
mUri = mUri.buildUpon().appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();
I am having problems with this NumberFormatException Invalid Long. The log cat is showing that the error is coming from the Long.parseLong part of the method.
public static String getDateTimeStr(String p_time_in_millis) {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_TIME_FORMAT);
Date l_time = new Date(Long.parseLong(p_time_in_millis));
return sdf.format(l_time);
}
Can someone tell me why this code works fine when I fetch and display the data in certain calendars then in other calendars on my device I get this NumberFormatException Invalid Long please?
Edit: Here is the rest of the codeā¦
private void getEvents() {
Uri l_eventUri;
ArrayList<Map<String, String>> allStudents = new ArrayList<Map<String, String>>();
if (Build.VERSION.SDK_INT >= 8) {
l_eventUri = Uri.parse("content://com.android.calendar/events");
} else {
l_eventUri = Uri.parse("content://calendar/events");
}
String[] l_projection = new String[]{"title", "dtstart", "dtend"};
#SuppressWarnings("deprecation")
Cursor l_managedCursor = this.managedQuery(l_eventUri, l_projection, "calendar_id=" + m_selectedCalendarId, null, "dtstart DESC, dtend DESC");
if (l_managedCursor.moveToFirst()) {
int l_colTitle = l_managedCursor.getColumnIndex(l_projection[0]);
int l_colBegin = l_managedCursor.getColumnIndex(l_projection[1]);
int l_colEnd = l_managedCursor.getColumnIndex(l_projection[2]);
String l_title = String.valueOf(l_colTitle);
String l_begin = Integer.toString(l_colBegin);
String l_end = Integer.toString(l_colEnd);
do {
Map<String, String> map = new HashMap<String, String>();
l_title = l_managedCursor.getString(l_colTitle);
l_begin = getDateTimeStr(l_managedCursor.getString(l_colBegin));
l_end = getDateTimeStr(l_managedCursor.getString(l_colEnd));
map.put("eventTitles", l_title);
map.put("event_begin", l_begin);
map.put("event_end", l_end);
allStudents.add(map);
} while (l_managedCursor.moveToNext());
l_managedCursor.close();
SimpleAdapter adapter = new SimpleAdapter(this, allStudents, R.layout.notice_layout, new String[] { "eventTitles", "event_begin", "event_end" }, new int[] { R.id.tvTitle, R.id.tvBody, R.id.tvTeacherCode});
listViewCalendar.setAdapter(adapter);
}
}
Edit 2:
For some reason the code works fine without this line of code so I've nailed it down to this line of code.
l_end = getDateTimeStr(l_managedCursor.getString(l_colEnd));
Why does the l_colEnd get caught up in an NumberFormatExcetion? When the following line of code could be also caught up in the same NumberFormatException because it is enquiring about the same int format?
l_begin = getDateTimeStr(l_managedCursor.getString(l_colBegin));
Thank you too all who have helped. The other interesting thing also is when I add this
int l_cnt = 0;
do {
++l_cnt;
} while (l_managedCursor.moveToNext() && l_cnt < 100);
to the while clause as shown below at the end of the following code the app works fine with no lines of code throwing a NumberFormatException..
if (l_managedCursor.moveToFirst()) {
int l_cnt = 0;
int l_colTitle = l_managedCursor.getColumnIndex(l_projection[0]);
int l_colBegin = l_managedCursor.getColumnIndex(l_projection[1]);
int l_colEnd = l_managedCursor.getColumnIndex(l_projection[2]);
String l_title = String.valueOf(l_colTitle);
String l_begin = Integer.toString(l_colBegin);
String l_end = Integer.toString(l_colEnd);
do {
Map<String, String> map = new HashMap<String, String>();
l_title = l_managedCursor.getString(l_colTitle);
l_begin = getDateTimeStr(l_managedCursor.getString(l_colBegin));
l_end = getDateTimeStr(l_managedCursor.getString(l_colEnd));
map.put("eventTitles", l_title);
map.put("event_begin", l_begin);
map.put("event_end", l_end);
allStudents.add(map);
++l_cnt;
} while (l_managedCursor.moveToNext() && l_cnt < 100);
Can someone tell me why this code works fine when I fetch and display
the data in certain calendars then in other calendars on my device I
get this NumberFormatException Invalid Long please?
Date l_time = new Date(Long.parseLong(p_time_in_millis));
IMO. This code is "bad" code.
Why? You try to fetch unknown data from unchecked sources
"content://com.android.calendar/events" and "content://calendar/events"
Almost everybody can access calendars and save whatever he like... Sadly there is no rule there for using this! So making a wild guess an app is using this event column to save data in another that your expected format.
Regarding the check l_cnt < 100, where fail stops
It's not failing because the error happens to 101 event or later!
My solution would to check my data, and never trust that other apps will act as I expected or as they should.
So I suggest to change getDateTimeStr method as follows:
public static String getDateTimeStr(String p_time_in_millis) {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_TIME_FORMAT);
long timestamp = 0;
try {
timestamp = Long.parseLong(p_time_in_millis)
} catch(NumberFormatException e) {
Log.w("getDateTimeStr", "Cannot convert '"+p_time_in_millis+"' to long");
e.printStackTrace(); // Prints full error exception
}
Date l_time = new Date(timestamp);
return sdf.format(l_time);
}
Remove the l_cnt < 100 check and leave the code to run checking your logcat! You will now have a better overview of what is happening and also your bad data dates will be 1/1/1970 (due to 0 timestamp) code can be changed responsively in order to handle that dates which does not have the expected format.
Some ideas for handling errors:
Make getDateTimeStr throw an exception to the getEvents() which could catch it and ignore the event with the unexpected data. (Handling logic, ignoring what I cannot understand.)
Recognize the format of the p_time_in_millis in each different case and use different DATE_TIME_FORMAT types regarding the exact format of each event. Okey, that's needs a lot of investigation and still can fail. Also you have to add the case that the format is still unknown so maybe ignore it or use a default value in order not to crash.
Generally, you always try to write a stable app that will not fail if another app saves data in a different format (because it like so or due to its own bug)
Big picture: GUI shows user a list of their playlists. User picks one. Program passes chosen playlist to next activity which displays the songs in that playlist.
Problem: I can display the playlists and register the users choice, but I can't seem to display the songs of that play list.
Yes, I've see the following questions:
How to query for songs in playlists on Android SDK?
Given an Android music playlist name, how can one find the songs in the playlist?
What is the String 'volumeName' argument of MediaStore.Audio.Playlists.Members.getContentUri referring to?
As you can see in my code, I've done my best to implement those solutions, but to no avail.
Things to keep in mind: I'm testing this on a Galaxy Nexus, so no SDcard. Just internal storage and music in the cloud. I need it to work in any scenario (internal, external, or cloud). It currently works in none of those.
//#SuppressWarnings ("serial)")
public class CreationActivity extends Activity {
private final String [] STAR= {"*"};
//reads in all songs to an array
#Override
public void onCreate (Bundle savedInstanceState){
super.onCreate(savedInstanceState);
//set layout view and assign to variable
setContentView(R.layout.creation);
TableLayout myLayout = (TableLayout)findViewById(R.id.creationLayout);
try {
Bundle extras = getIntent().getExtras();
if (extras!=null){
//get the desired playlist and ID
String playlist = extras.getString("playlist");
Long playlistID = extras.getLong("playlistID");
ArrayList<song> songs = new ArrayList<song>();
//read in the songs from the playlist
String[] proj = {MediaStore.Audio.Playlists.Members.TITLE,
MediaStore.Audio.Playlists.Members.ARTIST,
MediaStore.Audio.Playlists.Members.DURATION};
//method 1
Cursor songCursor = getContentResolver().query(MediaStore.Audio.Playlists.Members.getContentUri(null,playlistID),
proj,
null,
null,
null);
//method 2
/*
Cursor songCursor = getContentResolver().query(Uri.parse("content://com.google.android.music.MusicContent/playlists/members"),
proj,
null,
null,
null);
*/
//method 3
/*
Uri membersUri = MediaStore.Audio.Playlists.Members.getContentUri("internal", playlistID);
Cursor membersCursor = managedQuery(membersUri, STAR, null, null, null);
*/
//then this part with methods 1 and 2
/*
if (songCursor.getCount() > 0) {
songCursor.moveToFirst();
do {
song currSong = new song();
currSong.title = songCursor.getString(0);
currSong.artist = songCursor.getString(1);
songs.add(currSong);
} while (songCursor.moveToNext());
}
songCursor.close();
*/
//or this part with method 3
/*
membersCursor.moveToFirst();
for(int s= 0; s<membersCursor.getCount(); s++,
membersCursor.moveToNext()){
song currSong = new song();
currSong.title = songCursor.getString(0);
currSong.artist = songCursor.getString(1);
songs.add(currSong);
}
membersCursor.close();
*/
}else{
Toast.makeText(getBaseContext(), "No songs",Toast.LENGTH_LONG).show();
}
} catch (NumberFormatException e){
}
}
}
No errors during compiling. But "Unfortunately Music App has unexpectedly quit." every time.
Thanks for the help!
I figured it out. The key was to use the playlist ID as a string immediately within the URI. See code below.
This is the part that will get the playlist names and IDs:
String[] proj = {MediaStore.Audio.Playlists.NAME, MediaStore.Audio.Playlists._ID};
Uri playlistUri = Uri.parse("content://com.google.android.music.MusicContent/playlists");
Cursor playlistCursor = getContentResolver().query(playlistUri, proj, null, null, null);
if (playlistCursor.getCount() > 0) {
playlistCursor.moveToFirst();
do {
nameList.add(playlistCursor.getString(0));
idList.add(playlistCursor.getLong(1));
} while (playlistCursor.moveToNext());
}
Then once you have the playlist ID you can query for the songs in the playlist. This is the part of code that actually queries for the info and puts it all in an array list. NOTE: "song" is a class I have defined elsewhere, where readSong is a method that assigns values to various values (title, artist, etc).
ArrayList<song> songs = new ArrayList<song>();
//read songs into library from the correct playlist
String[] proj = {MediaStore.Audio.Playlists.Members.TITLE, MediaStore.Audio.Playlists.Members.ARTIST, MediaStore.Audio.Playlists.Members.DURATION, MediaStore.Audio.Playlists.Members._ID};
Uri songUri = Uri.parse("content://com.google.android.music.MusicContent/playlists/" + playlistID + "/members");
Cursor songCursor = getContentResolver().query(songUri, proj, null, null, null);
if (songCursor.getCount() > 0) {
songCursor.moveToFirst();
do {
//create dummy song
song currSong = new song();
//read info to dummy var
currSong.readSong(songCursor);
//add instance to collection
songs.add(currSong);
} while (songCursor.moveToNext());
}
songCursor.close();
I hope this helps anybody else who was struggling with this!! Let me know if you have any comments on my method or ways to make it better!
How do I play the "Positive" alarm sound? I know how to play the default one...
Ringtone ringtone = RingtoneManager.getRingtone(getApplicationContext(), RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM));
if (ringtone != null) {
ringtone.play();
}
EDIT:
Tried the below but the cursor just returns the same uri???
RingtoneManager rm = new RingtoneManager(getApplicationContext());
rm.setType(RingtoneManager.TYPE_ALARM);
Cursor c = rm.getCursor();
c.moveToFirst();
if (!c.isAfterLast()) {
do {
int uriIndex = c.getInt(RingtoneManager.URI_COLUMN_INDEX);
Uri ring = rm.getRingtoneUri(uriIndex);
Log.d("TC", ring.toString());
} while (c.moveToNext());
}
The RingtoneManager.getCursor() function provides a cursor that will let you iterate over all the ringtones and you can check the ringtone title for a matching name. The uri's returned depend on where the ringtone was stored but once the right one is found you can save the uri as a preference. Code I have (using a preference) doesn't do anything very fancy:
Uri alert;
String alarmname = mPrefs.getString(getString(R.string.pref_sound_key, null);
if (alarmname != null && !alarmname.equals(""))
alert = Uri.parse(alarmname);
elsewhere you can use the ringtone preferences to save a user choice and should be able to arrange the default to be "Positive" once you work out what the uri looks like.
RingtoneManager.URI_COLUMN_INDEX is a TEXT column.
RingtoneManager rm = new RingtoneManager(getApplicationContext());
rm.setType(RingtoneManager.TYPE_ALARM);
Cursor c = rm.getCursor();
c.moveToFirst();
if (!c.isAfterLast()) {
do {
int uriString = c.getString(RingtoneManager.URI_COLUMN_INDEX);
Log.d("TC", uriString);
} while (c.moveToNext());
}