My app displays playlists where for each playlist I show albumart in recyclerview.
A custom adapter displays the rows. It all works fine but when I add another playlist, using an asynctask, the display gets redrawn several times. I have checked that there are no adapter.notifydatasetchanged() calls. Stepping through the code I have discovered that it happens when there is a resolver.insert
In this example, the routine creates a new playlist, either for whole albums or tracks.
public void addTracksToPlaylist(Context context, String music_id,
long playlist_id, String nmode, int base) {
// feed it the album_id or track_id
Uri mediauri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
ContentResolver resolver = context.getContentResolver();
Uri exturi = MediaStore.Audio.Playlists.Members.getContentUri(
"external", playlist_id);
String[] projection = {MediaStore.Audio.Media._ID, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.TRACK};
String where = MediaStore.Audio.Media.ALBUM_ID + " =?";
String orderBy = null;
if (nmode.equals(context.getString(R.string.track_mode))) {
where = MediaStore.Audio.Media._ID + "=?";
}
orderBy = MediaStore.Audio.Media.ARTIST + " ASC, "
+ MediaStore.Audio.Media.ALBUM + " ASC , " +
"CAST(" + (MediaStore.Audio.Media.TRACK) + " AS INTEGER) ASC";
String[] whereVal = {music_id};
Cursor c = resolver.query(mediauri, projection, where, whereVal,
orderBy);
if (c != null && c.moveToFirst()) {
int idColumn = c.getColumnIndex(MediaStore.Audio.Media._ID);
ContentValues values = new ContentValues();
boolean stamp = prefs.getstampSelected(context);
for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) {
String audio_id = c.getString(idColumn);
values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, base);
values.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, audio_id);
try {
resolver.insert(exturi, values);
} catch (Exception e) {
e.printStackTrace();
}
// String thistrack= track.getfullPathfromAudioId(context,audio_id);
// Log.i(TAG,"position : "+base +" Inserted track = "+ audio_id + " track "+thistrack+ " Album_id = "+ music_id);
base++;
if (stamp) {
try {
track.updateTrackModifiedDate(audio_id, context);
} catch (Exception e) {
e.printStackTrace();
}
}
}
c.close();
}
}
What can I do to prevent these actions, which happen in the background, affect the display of playlists. Or in other words, why/how is my recyclerView "active" as it appears to be aware of changes in the database.
Update:
From the Android Developers website:
The Loader will monitor for changes to the data, and report them to you through new calls here. You should not monitor the data yourself. For example, if the data is a Cursor and you place it in a CursorAdapter, use the CursorAdapter(android.content.Context, android.database.Cursor, int) constructor without passing in either FLAG_AUTO_REQUERY or FLAG_REGISTER_CONTENT_OBSERVER (that is, use 0 for the flags argument). This prevents the CursorAdapter from doing its own observing of the Cursor, which is not needed since when a change happens you will get a new Cursor throw another call here.
the monitoring and reporting back by the loader manifests itself in onLoadFinished calls which in my case reset the adapter.
As was stated in the comments, the Loader monitors changes in the underlying data. The solution was simple in the end.
I declare a boolean variable
private boolean processing=false;
Set the variable to true in the onPreExecute() and to false in onPostExecute() of the AsyncTask.
As a change fires the OnLoadFinished call I test for the value of processing and skip the code. Once completed, I call getLoaderManager().restartLoader etc
Related
i know from Phonestatelistener we will get to know, the call type like its incoming, or ringing or picked etc.. But once call ends i.e once phone reaches idle state, i want to know what was the state of call, was it picked, missed or rejected
lets take, +91123456789 is an incoming call, after call ends, the number will be stored in phone call log as missed call,
is there a way to fetch the recent state from call log for the particular number +91123456789, is it possible?
Here is code that can query the call log for a missed call. Basically, you will have to trigger this and make sure that you give the call log some time ( a few seconds should do it) to write the information otherwise if you check the call log too soon you will not find the most recent call.
int MISSED_CALL_TYPE = android.provider.CallLog.Calls.MISSED_TYPE
final String[] projection = null;
final String selection = null;
final String[] selectionArgs = null;
final String sortOrder = android.provider.CallLog.Calls.DATE + " DESC";
Cursor cursor = null;
try{
// cursor = context.getContentResolver().query(
// Uri.parse("content://call_log/calls"),
// projection,
// selection,
// selectionArgs,
// sortOrder);
cursor = getContentResolver().query(CallLog.Calls.CONTENT_URI, null, CallLog.Calls.NUMBER + "=? ", yourNumber, sortOrder);
while (cursor.moveToNext()) {
String callLogID = cursor.getString(cursor.getColumnIndex(android.provider.CallLog.Calls._ID));
String callNumber = cursor.getString(cursor.getColumnIndex(android.provider.CallLog.Calls.NUMBER));
String callDate = cursor.getString(cursor.getColumnIndex(android.provider.CallLog.Calls.DATE));
String callType = cursor.getString(cursor.getColumnIndex(android.provider.CallLog.Calls.TYPE));
String isCallNew = cursor.getString(cursor.getColumnIndex(android.provider.CallLog.Calls.NEW));
if(Integer.parseInt(callType) == MISSED_CALL_TYPE && Integer.parseInt(isCallNew) > 0){
if (_debug) Log.v("Missed Call Found: " + callNumber);
break ;
}
}
}catch(Exception ex){
if (_debug) Log.e("ERROR: " + ex.toString());
}finally{
cursor.close();
}
I hope you find this useful, in the same way you can get other states.
Do add this permission in manifest
android.permission.READ_CONTACTS
i have implemented update() of ContentProvider and notifying to observer using getContext().getContentResolver().notifyChange(uri, null);
my obvious need is that whenever just one row is effected i want to notify with row specific uri, but could not find way to do so.
an additional query like "select id where selectionArgs" can do this but this will be a foolish way.
onchange(boolean, uri) get complete uri instead of specific row, easy to understand that this is because ContentProvider.update() is sending the same.
some code for more clarity
update() method of MyContentProvider
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Log.d("TAG", "update " + uri.getPath());
int count = 0;
switch (uriMatcher.match(uri)) {
case BOOKS:
count = booksDB.update(DATABASE_TABLE, values, selection, selectionArgs);
break;
case BOOK_ID:
count = booksDB.update(DATABASE_TABLE, values,
_ID + " = " + uri.getPathSegments().get(1)
+ (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (count == 1) {
Cursor c = query(uri, new String[] { _ID }, selection, selectionArgs, null);
long rowId = Long.valueOf(c.getString(c.getColumnIndex(_ID)));
uri = ContentUris.withAppendedId(CONTENT_URI, rowId);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
i will update table some how like
getContentResolver().update(MyContentProvider.CONTENT_URI, values1, MyContentProvider._ID+"<?", new String[]{"3"}));
frankly saying, code has barely related to question, just trying to give you some context
In your provider method, just return the uri with the id appended
#Override
public Uri insert(Uri uri, ContentValues values) {
Log.i(TAG, "insert " + uri);
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final int match = URI_MATCHER.match(uri);
Uri returnUri;
switch (match) {
case MESSAGE: {
long _id = db.insert(MessageContract.MessageEntry.TABLE_NAME, null, values);
if (_id > 0)
returnUri = ContentUris.withAppendedId(MessageContract.MessageEntry.CONTENT_URI, _id);
else
throw new android.database.SQLException("Failed to insert row into " + uri);
break;
}
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
getContext().getContentResolver().notifyChange(returnUri, null);
return returnUri;
}
And register your observer with true for descendents.
getContentResolver().registerContentObserver(MessageContract.MessageEntry.CONTENT_URI, true, mContentObserver);
To get the id from a Uri you can use ContentUris.parseId(uri)
Unfortunately I'm not able to suggest easy solution (because I'm not aware of full code and updates You need to run), there's some ways we You could try (some of them I've implemented in mine applications):
Provide ids in ContentValues - this way looks not applicable for Your case and it needs loop with calls to notifyChange();
Provide specific Uri for requests with queries (only some specific apps needs many various queries in selection, usually it's much easier to include query parameter in Uri). After another part of the program get notification with that specific Uri it will be able to check if it's 'current item' was updated and act appropriately (e.g. simplest case with list of articles and one article open in separate activity; then You update list of articles in the background from server You might need to update currently open article also and so, need to know if it was updated). You should be able to check particular item on the side of the observer using just received Uri, because it (Uri) will contain parameter(s) You've used for query;
You can pass the ID via ContentValues, and append it to the notification url. This way you don't have to make a separate query.
#Override
public int update(#NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int rows = _database.update(getTableName(), values, selection, selectionArgs);
if (rows > 0) {
Uri itemUri = ContentUris.withAppendedId(uri, values.getAsLong(DatabaseModel.COLUMN_ID)); // DatabaseModel.COLUMN_ID is "_id"
getContext().getContentResolver().notifyChange(itemUri, null);
}
return rows;
}
Whenever I want to add new data to an existing Android contact, I use the following function to retrieve all RawContacts IDs for the given contact ID:
protected ArrayList<Long> getRawContactID(String contact_id) {
ArrayList<Long> rawContactIDs = new ArrayList<Long>();
String[] projection = new String[] { ContactsContract.RawContacts._ID };
String where = ContactsContract.RawContacts.CONTACT_ID + " = ?";
String[] selection = new String[] { contact_id };
Cursor c = getContentResolver().query(ContactsContract.RawContacts.CONTENT_URI, projection, where, selection, null);
try {
while (c.moveToNext()) {
rawContactIDs.add(c.getLong(0));
}
}
finally {
c.close();
}
return rawContactIDs;
}
After that, I just insert the data using the ContentResolver:
getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values);
This is done for all RawContacts IDs that have been found previously. The effect is, of course, that all data is added repeatedly. Thus I want to return only one result now, but this has to meet special requirements.
I would like to adjust my function above so that its result meets the following requirements:
ContactsContract.RawContactsColumn.DELETED must be 0
The RawContacts entry must not be a secured one like Facebook's
ContactsContract.SyncColumns.ACCOUNT_TYPE is preferably "com.google". So if there is one entry that meets this requirement, it should be returned. If there is none, return any of the remaining entries.
How can I do this (most efficiently)? I don't want to make the query to complex.
I have given this some thought, from my experience with contact r/w, and with your needs in mind. I hope this helps you solve the issue and or points you in the direction you are looking for.
Please note that i have no device available with any sync adapters such as facebook so unfortunately i cannot confirm my answer viability (the read only bit mainly which might changeable to a simple != '' ).
Same getRawContactID function with some adjustments
protected ArrayList<Long> getRawContactID(String contact_id) {
HashMap<String,Long> rawContactIDs = new HashMap<String,Long>();
String[] projection = new String[] { ContactsContract.RawContacts._ID, ContactsContract.RawContacts.ACCOUNT_TYPE };
String where = ContactsContract.RawContacts.CONTACT_ID + " = ? AND " + ContactsContract.RawContacts.DELETED + " != 1 AND " + ContactsContract.RawContacts.RAW_CONTACT_IS_READ_ONLY + " != 1" ;
String[] selection = new String[] { contact_id };
Cursor c = getContentResolver().query(ContactsContract.RawContacts.CONTENT_URI, projection, where, selection, null);
try {
while (c.moveToNext()) {
rawContactIDs.put(c.getString(1),c.getLong(0));
}
}
finally {
c.close();
}
return getBestRawID(rawContactIDs);
}
And another getBestRawID function to find the best suited account -
protected ArrayList<Long> getBestRawID(Map<String,Long> rawContactIDs)
{
ArrayList<Long> out = new ArrayList<Long>();
for (String key : rawContactIDs.KeySet())
{
if (key.equals("com.google"))
{
out.clear(); // might be better to seperate handling of this to another function to prevent WW3.
out.add(rawContactIDs.get(key));
return out;
} else {
out.add(rawContactIDs.get(key));
}
}
return out;
}
Also note - I wrote most of the code without running / testing it. Apologies in advance.
I am trying to create an app which simply offers an edittext and imagebutton. If the butten gets clicked, the idea is that an album is added to the Playlist, named in the edittext box. Albums should be selected randomly. Goes without saying that the album tracks should be in the correct order.
I can add more functionality later eg. save, overwrite, delete etc.
I have the interface but am struggling with the code. I sort of get the concept of ContentProviders.
so the code needs to:
access the Playlists, which I believe is achieved by using MediaStore.Audio.Playlists
access the Albums, which I believe is achieved by using MediaStore.Audio.Albums
add to the Playlist
I have the following code (most bits obtained from this site. Thanks btw) to access the Playlist but it crashes with a Null Exception error.
public void checkforplaylists()
{
//Get a cursor over all playlists.
final ContentResolver resolver= MediaProvider.mContentResolver;
final Uri uri=MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI;
final String id=MediaStore.Audio.Playlists._ID;
final String name=MediaStore.Audio.Playlists.NAME;
final String[]columns={id,name};
final Cursor playlists= resolver.query(uri, columns, null, null, null);
if(playlists==null)
{
Log.e(TAG,"Found no playlists.");
return;
}
return;
}
Anyone who can help?
I think you mean NullPointerException, which means one of your assignments is null and then you try to access a member of the object you intended it to be. Most likely it is resolver, but to be sure you need the line number reported and/or to step through that with a debugger.
This works. When using the ContentResolver, the Context (this) is required.
public void checkforplaylists(Context context)
{
ContentResolver cr = context.getContentResolver();
final Uri uri=MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
final String id=MediaStore.Audio.Playlists._ID;
final String name=MediaStore.Audio.Playlists.NAME;
final String[]columns={id,name};
final Cursor playlists= cr.query(uri, columns, null, null, null);
if(playlists==null)
{
Log.e(TAG,"Found no playlists.");
return;
}
Log.e(TAG,"Found playlists.");
return;
}
use this code, will work
public boolean addPlaylist(String pname) {
Uri playlists = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
Cursor c = resolver.query(playlists, new String[] { "*" }, null, null,
null);
long playlistId = 0;
c.moveToFirst();
do {
String plname = c.getString(c
.getColumnIndex(MediaStore.Audio.Playlists.NAME));
if (plname.equalsIgnoreCase(pname)) {
playlistId = c.getLong(c
.getColumnIndex(MediaStore.Audio.Playlists._ID));
break;
}
} while (c.moveToNext());
c.close();
if (playlistId != 0) {
Uri deleteUri = ContentUris.withAppendedId(playlists, playlistId);
Log.d(TAG, "REMOVING Existing Playlist: " + playlistId);
// delete the playlist
resolver.delete(deleteUri, null, null);
}
Log.d(TAG, "CREATING PLAYLIST: " + pname);
ContentValues v1 = new ContentValues();
v1.put(MediaStore.Audio.Playlists.NAME, pname);
v1.put(MediaStore.Audio.Playlists.DATE_MODIFIED,
System.currentTimeMillis());
Uri newpl = resolver.insert(playlists, v1);
Log.d(TAG, "Added PlayLIst: " + newpl);
flag=true;
return flag;
}
i'm creating a contentProvider , and i wish to be able to send it multiple DB records (contentValues) to be inserted or updated to a single table using a single batch operations .
how do i do that?
batchInsert is intended only for inserting , but wouldn't it mean that insertion of something that already exists won't do anything?
also , is there a way for the update operation to use a special constraint ? for example , i need to ignore the primary key and update based on 2 other fields that together are unique.
"batchInsert is intended only for inserting" : this is true BUT you can override it in your ContentProvider to perform an UPSERT (insert/update) depending on the URI passed to batchInsert.
The following is some working code that I currently use to perform bulk inserts on time-series data (admittedly, I just delete anything that gets in the way instead of updating, but you could easily change this to your own ends.).
Also note the use of the sql transaction; this speeds up the process immensely.
#Override
public int bulkInsert(Uri uri, ContentValues[] values) {
SQLiteDatabase sqlDB = database.getWritableDatabase();
switch (match(uri)) {
case ONEPROGRAMME:
String cid = uri.getLastPathSegment();
int insertCount = 0;
int len = values.length;
if (len > 0) {
long start = values[0].getAsLong(Programme.COLUMN_START);
long end = values[len - 1].getAsLong(Programme.COLUMN_END);
String where = Programme.COLUMN_CHANNEL + "=? AND " + Programme.COLUMN_START + ">=? AND "
+ Programme.COLUMN_END + "<=?";
String[] args = { cid, Long.toString(start), Long.toString(end) };
//TODO use a compiled statement ?
//SQLiteStatement stmt = sqlDB.compileStatement(INSERT)
sqlDB.beginTransaction();
try {
sqlDB.delete(tableName(PROGRAMME_TABLE), where, args);
for (ContentValues row : values) {
if (sqlDB.insert(tableName(PROGRAMME_TABLE), null, row) != -1L) {
insertCount++;
}
}
sqlDB.setTransactionSuccessful();
} finally {
sqlDB.endTransaction();
}
}
if (insertCount > 0)
getContext().getContentResolver().notifyChange(Resolver.PROGRAMME.uri, null);
return insertCount;
default:
throw new UnsupportedOperationException("Unsupported URI: " + uri);
}
}