I have created an app in which I use a ContentProvider I created to keep track of some photos my app creates.
The concept is simple.
I capture some Images and save the Image name, date, and path to a ContentProvider.
And then I use this content provider to load each time thumbnails etc from this ContentProvider when my app starts etc.
I use a CursorAdapter and ListActivity for the full picture.
If the user deletes the image from a file manager or through my app, I want to delete that entry from the ContentProvider.
I have this method which is supposed to do so.
private void deletePhotoFromContentProvider(String path){
String[] selectionArgs=new String[]{path};
Log.i(TAG, "Path to delete:" + path);
String whereClause=""+PhotosContract.PHOTO_FILE_PATH+"=?";
int res = mContext.getContentResolver().delete(PhotosContract.CONTENT_URI,
whereClause, selectionArgs);
Log.i(TAG, "Deleted :" + Integer.toString(res));
mContext.getContentResolver().notifyChange(PhotosContract.CONTENT_URI, null);
}
My table has an ID, a photo_name, photo_date, photo_file_path.
I perform the delete based on the file_path which is basically unique.
I checked the path and the Log prints the correct one, but when i try to delete this deletes all my entries. Note that the
ContentValues values = initValues();
mContext.getContentResolver().insert(PhotosContract.CONTENT_URI, values);
works properly.
This is also my PhotosContract class
public class PhotosContract {
public static final String AUTHORITY = "com.android.testapp.provider";
public static final Uri BASE_URI = Uri
.parse("content://" + AUTHORITY + "/");
public static final String PHOTOS_TABLE_NAME = "photos";
// The URI for this table.
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_URI,
PHOTOS_TABLE_NAME);
public static final String _ID = "_id";
public static final String PHOTO_FILE_PATH = "photo_file_path";
public static final String PHOTO_FILE_NAME = "photo_name";
public static final String PHOTO_DATE = "photo_date";
}
I also tried to use something like this:
String whereClause=""+PhotosContract.PHOTO_FILE_PATH+"=" + path;
int res = mContext.getContentResolver().delete(PhotosContract.CONTENT_URI,
whereClause, null);
but does not work either. It also deletes all my ContentProvider entries.
Any help?
Thanks in advance.
EDIT:
I am posting my delete method from my ContentProvider:
#Override
public int delete(Uri arg0, String arg1, String[] arg2) {
int rowsDeleted = mDbHelper.getWritableDatabase().delete(
PhotosContract.PHOTOS_TABLE_NAME, arg1, arg2);
getContext().getContentResolver().notifyChange(
PhotosContract.CONTENT_URI, null);
return rowsDeleted;
}
Related
I have a simple question.
It's possible to access to DCIM folder (internal storage) from an app and retrieve an image?
I've found solutions that use something like Enviroment.getExternalStoragePublicDirectory but I don't understand why.
Environment.getExternalStorageDirectory().toString()+ "/DCIM/Camera";
or
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath()
Will give you a path for the public directory. This path is required to fetch all files including image. getExternalStorageDirectory() and Environment.getExternalStoragePublicDirectory() is public so you can access them from any installed app.
The following code snippet can be used to list all image file path from "DCIM" folder:
public static List<String> getCameraImages(Context context) {
public final String CAMERA_IMAGE_BUCKET_NAME = Environment.getExternalStorageDirectory().toString()+ "/DCIM/Camera";
public final String CAMERA_IMAGE_BUCKET_ID = String.valueOf(CAMERA_IMAGE_BUCKET_NAME.toLowerCase().hashCode());
final String[] projection = { MediaStore.Images.Media.DATA };
final String selection = MediaStore.Images.Media.BUCKET_ID + " = ?";
final String[] selectionArgs = { CAMERA_IMAGE_BUCKET_ID };
final Cursor cursor = context.getContentResolver().query(Images.Media.EXTERNAL_CONTENT_URI,
projection,
selection,
selectionArgs,
null);
ArrayList<String> result = new ArrayList<String>(cursor.getCount());
if (cursor.moveToFirst()) {
final int dataColumn =
cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
do {
final String data = cursor.getString(dataColumn);
result.add(data);
} while (cursor.moveToNext());
}
cursor.close();
return result;
}
I need to update the column,i am able to add properly but I am not able to update.
Here is my code for updating.
public void setStatus(boolean status,int id,Context c){
InitilizeSqlite(c);
SQLiteDatabase db=userHelper.getReadableDatabase();
Cursor cursor = db.query(ReminderDetails.ReminderItem.Table_Name,new String[]{
ReminderDetails.ReminderItem.Id,
ReminderDetails.ReminderItem.Date,
ReminderDetails.ReminderItem.Time,
ReminderDetails.ReminderItem.Medicine,
ReminderDetails.ReminderItem.Reminder_Status}
,ReminderDetails.ReminderItem.Id+ "=?",
new String[] {String.valueOf(id)}, null, null, null, null);
if (cursor != null)
cursor.moveToFirst();
int getId=Integer.parseInt(cursor.getString(0));
String date=cursor.getString(1);
String time=cursor.getString(2);
String medicine=cursor.getString(3);
getstatus=Boolean.parseBoolean(cursor.getString(4));
SQLiteDatabase dbUpdate=userHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(ReminderDetails.ReminderItem.Id,getId);
values.put(ReminderDetails.ReminderItem.Date,date);
values.put(ReminderDetails.ReminderItem.Time,time);
values.put(ReminderDetails.ReminderItem.Medicine,medicine);
values.put(ReminderDetails.ReminderItem.Reminder_Status,status);
dbUpdate.update(ReminderDetails.ReminderItem.Table_Name,values,ReminderDetails.ReminderItem.Id+ "=?",new String[]{String.valueOf(id)});
}
//Here is my column Detail
public class ReminderDetails {
public static abstract class ReminderItem{
public static final String Id="item_id";
public static final String Time="time";
public static final String Date="date";
public static final String Medicine="medicine";
public static final String Table_Name="reminder_details";
public static final String Reminder_Status="reminder_status";
}
}
Can any one help to me address what is the issue why am not able to update reminder status ?
I found the solution actually values.put(ReminderDetails.ReminderItem.Reminder_Status,status) here I was passing status in boolean but in my table I saved string so I just change to values.put(ReminderDetails.ReminderItem.Reminder_Status,"true") then its working fine. Thank you all who give time for this question.
codes:
First my Uris
public static final String PACKAGE = "my.url.contentprovider";
public static final String TABLE_NAME = "NetworkTransaction";
public static final String AUTHORITY = PACKAGE + ".NetTransContentProvider";
public static final Uri BASE_URI = Uri.parse("content://"+AUTHORITY);
public static final Uri CONTENT_URI_ANY_OBSERVER = Uri.withAppendedPath(BASE_URI,TABLE_NAME+"/*");
public static final Uri CONTENT_URI_FIND_BY_ID = Uri.withAppendedPath(BASE_URI,TABLE_NAME+"/FIND/ID");
public static final Uri CONTENT_URI_INSERT_OR_REPLACE_BY_ID = Uri.withAppendedPath(BASE_URI,TABLE_NAME+"/INSERT/REPLACE/ID");
public static final Uri CONTENT_URI_INSERT_BY_ID = Uri.withAppendedPath(BASE_URI,TABLE_NAME+"/INSERT/ID");
and my loader from activity code:
#Override
protected void onResume() {
super.onResume();
getSupportLoaderManager().restartLoader(NET_TRANS_LOADER_ID,mBundle,this).forceLoad();
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle bundle) {
Uri uri = null;
CursorLoader cl=null;
switch (id) {
case NET_TRANS_LOADER_ID:
uri = NetTransContentProvider.CONTENT_URI_FIND_BY_ID;
cl = new CursorLoader(ChoosingUserNameActivity.this, uri, NetTransDbUtils.allColumns,
NetTransDbUtils.COLUMN_ID + " = ? ",
new String[]{String.valueOf(bundle.getLong(EXTRA_TRANSACTION_ID,-1))}, null);
break;
default:
break;
}
return cl;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
final int loaderId = loader.getId();
switch (loaderId) {
case NET_TRANS_LOADER_ID:
if(mTransactionId != null){
NetTrans netTrans = NetTransDbUtils.cursorToNetTrans(cursor);
if(netTrans != null && netTrans.getStatus() != null
&& !netTrans.getStatus().equals(NetTrans.STATUS_PENDING)){
EventBus.getDefault().post(new NetTransMsg(true, mTransactionId, netTrans.getMessage()));
}
}
break;
default:
break;
}
}
and at a runnable that runs on ExecutorService in a started service I call
mContext.getContentResolver().insert(NetTransContentProvider.CONTENT_URI_INSERT_OR_REPLACE_BY_ID, cv );
the value inserted but the loader dose not call:
#Override
public Uri insert(Uri uri, ContentValues values) {
int uriType = sUriMatcher.match(uri);
switch (uriType) {
case INSERT_OR_REPLACE_BY_ID:
mDatabase.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
break;
case INSERT_BY_ID:
mDatabase.insert(TABLE_NAME, null, values);
break;
default:
break;
}
getContext().getContentResolver().notifyChange(CONTENT_URI_ANY_OBSERVER, null);
return null;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder) {
Cursor cursor = mDatabase.query(TABLE_NAME,projection,selection, selectionArgs, null, null, null);
cursor.setNotificationUri(getContext().getContentResolver(), CONTENT_URI_ANY_OBSERVER);
return cursor;
}
my problem is getContext().getContentResolver().notifyChange(CONTENT_URI_ANY_OBSERVER, null); in the insert method can not make my loader restart.
UPDATE
I have created a sample project, you press the button, a new NetTrans object is created and written into database, then the thread sleeps 5000 ms and overwrites that value (to simulate network operation). but after that loader dose not restart. Where is my bug ?
If you want observers registered on CONTENT_URI_ANY_OBSERVER to be notified when a change happens on CONTENT_URI_FIND_BY_ID, you need to make sure of two things.
First, the CONTENT_URI_ANY_OBSERVER needs to be a parent of CONTENT_URI_FIND_BY_ID. If you think of it like folders on a file system, 'CONTENT_URI_ANY_OBSERVER' should contain `CONTENT_URI_FIND_BY_ID' in one of its sub folders.
Second, you must pass true for the notifyDescendants argument when registering your content observer.
There is no wild card considerations when Android attempts to find matching content observers (the link provided in the comments only applies to the UriMatcher). So, to fix your problem you should remove the /* from your CONTENT_URI_ANY_OBSERVER and it should start matching. You can see how my.url.contentprovider/NetworkTransaction is now a parent "folder" of my.url.contentprovider/NetworkTransaction/INSERT/REPLACE/ID where as before you had my.url.contentprovider/NetworkTransaction/*.
EDIT 1
After reviewing your sample project, I have found the other area where your problem lies. When you use a cursor loader, the cursor is owned by the loader. This means that you shouldn't alter it in anyway aside from just iterating through its data. In your NetTransDbUtils.cursorToNetTrans(cursor) method, you are closing your cursor which will prevent the CursorLoader from being able to effectively monitor changes to your cursor's data.
Simple answer: Don't call close cursor.close() in NetTransDbUtils.cursorToNetTrans(cursor); for this use case.
Replace your Uris with a simple parse:
public static final Uri CONTENT_URI_ANY_OBSERVER = Uri
.parse(BASE_URI + "/" + TABLE_NAME);
public static final Uri CONTENT_URI_FIND_BY_ID = Uri
.parse(BASE_URI + "/" + TABLE_NAME + "/FIND/ID");
public static final Uri CONTENT_URI_INSERT_OR_REPLACE_BY_ID = Uri
.parse(BASE_URI + "/" + TABLE_NAME + "/INSERT/REPLACE/ID");
public static final Uri CONTENT_URI_INSERT_BY_ID = Uri
.parse(BASE_URI + "/" + TABLE_NAME + "/INSERT/ID");
In your RestService class, use a handler for callbacks:
private Handler mHandler;
#Override
public void onCreate() {
...
mHandler = new Handler();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
...
mHandler.post(task);
...
}
I have setup a fragment to pull data from a custom content provider using a CursorLoader.
The problem is that when i update a record in the SQLite table using the content resolver, the cursor does not refresh i.e. the getContext().getContentResolver().notifyChange(myUri, null) has no effect. I have to exit the fragment and open it again to see the change.
I think the problem is that the URI i have used to update a row is not being observed by the loader :
URI to create loader -content://com.myapp.provider/MyTable/Set/22
URI to update row -content://com.myapp.provider/MyTable/167
167 identifies a unique row in the table. 22 identifies a set of rows in the table. Is there some way to tell the loader that the row 167 comes within the set 22, so it should reset the cursor?
Here is the code in case it brings more clarity :
Creating CursorLoader in Fragment :
#Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle queryBundle) {
CursorLoader cursorLoader = new CursorLoader(getActivity(), Uri.parse("content://com.myapp.provider/MyTable/Set/22"), myProjection, null, null, null);
return cursorLoader;
}
on button click in fragment :
mContext.getContentResolver().update("content://com.myapp.provider/MyTable/167", values, null, null);
Content Provider class :
private static final String AUTHORITY = "com.myapp.provider";
private static final String TABLE_PATH = "MyTable";
public static final String CONTENT_URI_BASEPATH = "content://" + AUTHORITY + "/" + TABLE_PATH;
private static final int URITYPE_TABLE = 1;
private static final int URITYPE_SINGLE_SET = 2;
private static final int URITYPE_SINGLE_ROW = 3;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static{
sUriMatcher.addURI(AUTHORITY, TABLE_PATH,URITYPE_TABLE);
sUriMatcher.addURI(AUTHORITY, TABLE_PATH + "/Set/#", URITYPE_SINGLE_SET);
sUriMatcher.addURI(AUTHORITY, TABLE_PATH + "/#", URITYPE_SINGLE_ROW);
}
#Override
public int update(Uri myUri, ContentValues values, String selection, String[] selectionArgs){
int rowCount = 0;
String id;
SQLiteDatabase db = localDB.getWritableDatabase();
int uriType = sUriMatcher.match(myUri);
switch(uriType){
case URITYPE_SINGLE_ROW :
id = uri.getLastPathSegment();
//selection and selectionArgs are ignored since the URI itself identifies a unique row.
rowCount = db.update(MyTable.TABLE_NAME, values, MyTable.COLUMN_ID + " = ?", new String[] {id});
}
getContext().getContentResolver().notifyChange(myUri, null);
return rowCount;
}
I has a similar issue and found the solution here.
In brief, it turned out I needed to call setNotificationUri(ContentResolver cr, Uri uri) on the cursor returned by the query() method of my content provider.
The solution is to call notifyChange() on the Uri that is being observed i.e. the set and not on the row.
To achieve this, we need to make some changes :
Include the set ID in the URI when calling the update :
mContext.getContentResolver().update("content://com.myapp.provider/MyTable/Set/22/167", values, null, null);
Change the URI pattern of a single row from "/#" to "/Set/#/#"
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static{
sUriMatcher.addURI(AUTHORITY, TABLE_PATH,URITYPE_TABLE);
sUriMatcher.addURI(AUTHORITY, TABLE_PATH + "/Set/#", URITYPE_SINGLE_SET);
sUriMatcher.addURI(AUTHORITY, TABLE_PATH + "/Set/#/#", URITYPE_SINGLE_ROW);
}
Then in the update function, construct a new Uri that has to be notified :
List<String> pathSegments = uri.getPathSegments();
String mySetID = pathSegments.get(2);
Uri mySetUri = Uri.parse("content://" + AUTHORITY + "/" + TABLE_PATH + "/Set/" + mySetID);
getContext().getContentResolver().notifyChange(mySetUri, null);
I am writing an application to sync contact details with my server. For this I need to query Contacts using content provider multiple times as all the data is not present in one table. For example by using contact id, I have to query separately to phone, email and address tables for each contact which I feel is not much efficient. It would be really helpful if someone could point me out ways to get all the contact details in a single query. Thanks in advance :)
If you have the rawContactId you don't need multiple query. You can have a single query with Data.CONTENT_URI as uri and with a selection on your rawContactId.
The you have to loop with the resulting cursor to read the info. To know in wich column you need to see for a given row in the Data table you need to check the MIMETYPE
EDIT
private interface DataQuery {
public static final String[] PROJECTION = new String[] { Data._ID, Data.MIMETYPE, Data.DATA1, Data.DATA2, Data.DATA3, };
public static final int COLUMN_ID = 0;
public static final int COLUMN_MIMETYPE = 1;
public static final int COLUMN_DATA1 = 2;
public static final int COLUMN_DATA2 = 3;
public static final int COLUMN_DATA3 = 4;
public static final int COLUMN_PHONE_NUMBER = COLUMN_DATA1;
public static final int COLUMN_PHONE_TYPE = COLUMN_DATA2;
public static final int COLUMN_GIVEN_NAME = COLUMN_DATA2;
public static final int COLUMN_FAMILY_NAME = COLUMN_DATA3;
public static final String SELECTION = Data.RAW_CONTACT_ID + "=?";
}
final Cursor c = resolver.query(Data.CONTENT_URI, DataQuery.PROJECTION, DataQuery.SELECTION, new String[] { String.valueOf(rawContactId) }, null);
try {
while (c.moveToNext()) {
final long id = c.getLong(DataQuery.COLUMN_ID);
final String mimeType = c.getString(DataQuery.COLUMN_MIMETYPE);
uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
if (mimeType.equals(StructuredName.CONTENT_ITEM_TYPE)) {
final String oldLastName = c.getString(DataQuery.COLUMN_FAMILY_NAME);
final String oldFirstName = c.getString(DataQuery.COLUMN_GIVEN_NAME);
//store them where you need
} else if (mimeType.equals(Phone.CONTENT_ITEM_TYPE)) {
final int type = c.getInt(DataQuery.COLUMN_PHONE_TYPE);
final String cellPhone = c.getString(DataQuery.COLUMN_PHONE_NUMBEIR);
//store them where you need
}
}
} // while
} finally {
if (c!=null) c.close();
}
Please consider that I did not check the code: I do not have a compiler here. I hope it will be useful anyway
Data is a generic table that can hold any kind of contact data.
The kind of data stored in a given row is specified by the row's MIMETYPE value, which determines the meaning of the generic columns DATA1 through DATA15.
For example, if the data kind is Phone.CONTENT_ITEM_TYPE, then the column DATA1 stores the phone number, but if the data kind is Email.CONTENT_ITEM_TYPE, then DATA1 stores the email address. Sync adapters and applications can introduce their own data kinds.