android forensic - android

I would like to retrieve the browser history from the com.android.browser and save it into a file in sdcard. I am able to do soo for the contacts but not the browser history.Don't know what went wrong. They only prompt the error bind or column index out of range: handle 0x27a6d0.
What would be the best format for the file to be xml, txt or??
These are my codes
package fypj.ReadContacts;
import android.app.Activity;
import android.os.Bundle;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import android.app.Activity;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Browser;
import android.provider.Browser.BookmarkColumns;
import android.provider.ContactsContract;
import android.util.Log;
import android.widget.TextView;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
public class ReadContacts extends Activity {
// To suppress notational clutter and make structure clearer, define some shorthand constants.
private static final Uri URI = ContactsContract.Contacts.CONTENT_URI;
private static final Uri PURI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
private static final Uri EURI = ContactsContract.CommonDataKinds.Email.CONTENT_URI;
private static final Uri AURI = ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_URI;
private static final String ID = ContactsContract.Contacts._ID;
private static final String DNAME = ContactsContract.Contacts.DISPLAY_NAME;
private static final String HPN = ContactsContract.Contacts.HAS_PHONE_NUMBER;
private static final String LOOKY = ContactsContract.Contacts.LOOKUP_KEY;
private static final String CID = ContactsContract.CommonDataKinds.Phone.CONTACT_ID;
private static final String EID = ContactsContract.CommonDataKinds.Email.CONTACT_ID;
private static final String AID = ContactsContract.CommonDataKinds.StructuredPostal.CONTACT_ID;
private static final String PNUM = ContactsContract.CommonDataKinds.Phone.NUMBER;
private static final String PHONETYPE = ContactsContract.CommonDataKinds.Phone.TYPE;
private static final String EMAIL = ContactsContract.CommonDataKinds.Email.DATA;
private static final String EMAILTYPE = ContactsContract.CommonDataKinds.Email.TYPE;
private static final String STREET = ContactsContract.CommonDataKinds.StructuredPostal.STREET;
private static final String CITY = ContactsContract.CommonDataKinds.StructuredPostal.CITY;
private static final String STATE = ContactsContract.CommonDataKinds.StructuredPostal.REGION;
private static final String POSTCODE = ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE;
private static final String COUNTRY = ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY;
private static final Uri URI1 = android.provider.Browser.BOOKMARKS_URI;
private static final String bHistoryTITLE = Browser.BookmarkColumns.TITLE;
private static final String bHistoryURL = Browser.BookmarkColumns.URL;
private static final String bHistoryBOOKMARK = Browser.BookmarkColumns.BOOKMARK;
private static final String bHistoryVISITS = Browser.BookmarkColumns.VISITS;
private static final String ID1 = Browser.BookmarkColumns._ID ;
//private static final String BID = Browser.BookmarkColumns.
//private static final String DNAME = Browser.BookmarkColumns.DISPLAY_NAME;
//private static final String HPN = ContactsContract.Contacts.HAS_PHONE_NUMBER;
//private static final String LOOKY = Browser.BookmarkColumns.LOOKUP_KEY;
private String id;
private String lookupKey;
private String name;
private String street;
private String city;
private String state;
private String postcode;
private String country;
private String ph[];
private String phType[];
private String em[];
private String emType[];
private String id1;
private String URL;
private String BTitle;
private String BURL;
private String BBOOKMARK;
private String BVISITS;
private String browserTITLE[];
private String browserURL[];
private String browserBOOKMARK[];
private String browserVISITS[];
private File root;
private int emcounter;
private int phcounter;
private int addcounter;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Check that external media available and writable
checkExternalMedia();
Contacts();
browser();
}
/** Method to check whether external media available and writable and to find the
root of the external file system. */
private void checkExternalMedia () {
// Check external media availability. This is adapted from
// http://developer.android.com/guide/topics/data/data-storage.html#filesExternal
boolean mExternalStorageAvailable = false;
boolean mExternalStorageWriteable = false;
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
// We can read and write the media
mExternalStorageAvailable = mExternalStorageWriteable = true;
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
// We can only read the media
mExternalStorageAvailable = true;
mExternalStorageWriteable = false;
} else {
// Can't read or write
mExternalStorageAvailable = mExternalStorageWriteable = false;
}
// Find the root of the external storage and output external storage info to screen
root = android.os.Environment.getExternalStorageDirectory();
}
public void Contacts(){
// Allow for up to 5 email and phone entries for a contact
em = new String[5];
emType = new String[5];
ph = new String[5];
phType = new String[5];
/** Open a PrintWriter wrapping a FileOutputStream so that we can send output from a
query of the Contacts database to a file on the SD card. Must wrap the whole thing
in a try-catch to catch file not found and i/o exceptions. Note that since we are writing
to external media we must add a WRITE_EXTERNAL_STORAGE permission to the
manifest file. Otherwise a FileNotFoundException will be thrown. */
// This will set up output to /sdcard/download/phoneData.txt if /sdcard is the root of
// the external storage. See the project WriteSDCard for more information about
// writing to a file on the SD card.
File dir = new File (root.getAbsolutePath() + "/download");
dir.mkdirs();
//File file = new File(dir, "phoneData.rtf");
File file = new File(dir, "phoneData.accdb");
try{
FileOutputStream f = new FileOutputStream(file);
PrintWriter pw = new PrintWriter(f);
// Main loop to query the contacts database, extracting the information. See
// http://www.higherpass.com/Android/Tutorials/Working-With-Android-Contacts/
ContentResolver cr = getContentResolver();
Cursor cu = cr.query(URI, null, null, null, null);
if (cu.getCount() > 0) {
// Loop over all contacts
while (cu.moveToNext()) {
// Initialize storage variables for the new contact
street = "";
city = "";
state = "";
postcode = "";
country = "";
// Get ID information (id, name and lookup key) for this contact. id is an identifier
// number, name is the name associated with this row in the database, and
// lookupKey is an opaque value that contains hints on how to find the contact
// if its row id changed as a result of a sync or aggregation.
id = cu.getString(cu.getColumnIndex(ID));
name = cu.getString(cu.getColumnIndex(DNAME));
lookupKey = cu.getString(cu.getColumnIndex(LOOKY));
// Append list of contacts to the scrollable TextView on the screen.
// Query phone numbers for this contact (may be more than one), so use a
// while-loop to move the cursor to the next row until moveToNext() returns
// false, indicating no more rows. Store the results in arrays since there may
// be more than one phone number stored per contact. The if-statement
// enclosing everything ensures that the contact has at least one phone
// number stored in the Contacts database.
phcounter = 0;
if (Integer.parseInt(cu.getString(cu.getColumnIndex(HPN))) > 0) {
Cursor pCur = cr.query(PURI, null, CID + " = ?", new String[]{id}, null);
while (pCur.moveToNext()) {
ph[phcounter] = pCur.getString(pCur.getColumnIndex(PNUM));
phType[phcounter] = pCur.getString(pCur.getColumnIndex(PHONETYPE));
phcounter ++;
}
pCur.close();
}
// Query email addresses for this contact (may be more than one), so use a
// while-loop to move the cursor to the next row until moveToNext() returns
// false, indicating no more rows. Store the results in arrays since there may
// be more than one email address stored per contact.
emcounter = 0;
Cursor emailCur = cr.query(EURI, null, EID + " = ?", new String[]{id}, null);
while (emailCur.moveToNext()) {
em[emcounter] = emailCur.getString(emailCur.getColumnIndex(EMAIL));
emType[emcounter] = emailCur.getString(emailCur.getColumnIndex(EMAILTYPE));
emcounter ++;
}
emailCur.close();
// Query Address (assume only one address stored for simplicity). If there is
// more than one address we loop through all with the while-loop but keep
// only the last one.
addcounter = 0;
Cursor addCur = cr.query(AURI, null, AID + " = ?", new String[]{id}, null);
while (addCur.moveToNext()) {
street = addCur.getString(addCur.getColumnIndex(STREET));
city = addCur.getString(addCur.getColumnIndex(CITY));
state = addCur.getString(addCur.getColumnIndex(STATE));
postcode = addCur.getString(addCur.getColumnIndex(POSTCODE));
country = addCur.getString(addCur.getColumnIndex(COUNTRY));
addcounter ++;
}
addCur.close();
// Write identifiers for this contact to the SD card file
//pw.println(name+" ID="+id+" LOOKUP_KEY="+lookupKey);
pw.println(name);
// Write list of phone numbers for this contact to SD card file
for(int i=0; i<phcounter; i++){
//pw.println(" phone="+ ph[i]+" type="+phType[i] + " ("
//+ getPhoneType(phType[i]) + ") ");
pw.println(" phone="+ ph[i]+" ("
+ getPhoneType(phType[i]) + ") ");
}
// Write list of email addresses for this contact to SD card file
for(int i=0; i<emcounter; i++){
pw.println(" email="+ em[i]+" type="+emType[i] + " ("
+ getEmailType(emType[i]) + ") ");
}
// If street address is stored for contact, write it to SD card file
if(addcounter > 0){
if(street != null) pw.println(" street="+street);
if(city != null) pw.println(" city="+city);
if(state != null) pw.println(" state/region="+state);
if(postcode != null) pw.println(" postcode="+postcode);
if(country != null) pw.println(" country="+country);
}
}
}
// Flush the PrintWriter to ensure everything pending is output before closing
pw.flush();
pw.close();
f.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
Log.i("MEDIA", "*************** File not found. Did you" +
" add a WRITE_EXTERNAL_STORAGE permission to the manifest file? ");
} catch (IOException e) {
e.printStackTrace();
}
}
/** Method to return label corresponding to phone type code. Data for correspondence from
http://developer.android.com/reference/android/provider/ContactsContract.CommonDataKinds.Phone.html */
private String getPhoneType(String index){
if(index.trim().equals( "1")){
return "home";
} else if (index.trim().equals("2")){
return "mobile";
} else if (index.trim().equals("3")){
return "work";
} else if (index.trim().equals("7")){
return "other";
} else {
return "?";
}
}
/** Method to return label corresponding to email type code. Data for correspondence from
http://developer.android.com/reference/android/provider/ContactsContract.
CommonDataKinds.Email.html */
private String getEmailType(String index){
if(index.trim().equals( "1")){
return "home";
} else if (index.trim().equals("2")){
return "work";
} else if (index.trim().equals("3")){
return "other";
} else if (index.trim().equals("4")){
return "mobile";
} else {
return "?";
}
}
public void browser(){
//create file
File dir = new File (root.getAbsolutePath() + "/DATASTOREAGE");
dir.mkdirs();
File file = new File(dir, "browserHistory.rtf");
try{
FileOutputStream f = new FileOutputStream(file);
PrintWriter pw = new PrintWriter(f);
// Main loop to query the contacts database, extracting the information. See
// http://www.higherpass.com/Android/Tutorials/Working-With-Android-Contacts/
ContentResolver cr = getContentResolver();
Cursor cu = cr.query(URI1, null, null, null, null);
if (cu.getCount() > 0) {
// Loop over all contacts
while (cu.moveToNext()) {
// Initialize storage variables for the new contact
BTitle = "";
BURL = "";
BBOOKMARK = "";
BVISITS = "";
// Get ID information (id, name and lookup key) for this contact. id is an identifier
// number, name is the name associated with this row in the database, and
// lookupKey is an opaque value that contains hints on how to find the contact
// if its row id changed as a result of a sync or aggregation.
id1 = cu.getString(cu.getColumnIndex(ID1));
URL = cu.getString(cu.getColumnIndex(BookmarkColumns.TITLE));
//lookupKey = cu.getString(cu.getColumnIndex(LOOKY));
// Append list of contacts to the scrollable TextView on the screen.
// Query phone numbers for this contact (may be more than one), so use a
// while-loop to move the cursor to the next row until moveToNext() returns
// false, indicating no more rows. Store the results in arrays since there may
// be more than one phone number stored per contact. The if-statement
// enclosing everything ensures that the contact has at least one phone
// number stored in the Contacts database.
phcounter = 0;
Cursor bCur = cr.query(URI1, null, ID1, new String[]{id1}, null);
while (bCur.moveToNext()) {
browserTITLE[phcounter] = bCur.getString(bCur.getColumnIndex(bHistoryTITLE));
browserURL[phcounter] = bCur.getString(bCur.getColumnIndex(bHistoryURL));
browserBOOKMARK[phcounter] = bCur.getString(bCur.getColumnIndex(bHistoryBOOKMARK));
browserVISITS[phcounter] = bCur.getString(bCur.getColumnIndex(bHistoryVISITS));
phcounter ++;
}
bCur.close();
}
// Write identifiers for this contact to the SD card file
//pw.println(name+" ID="+id+" LOOKUP_KEY="+lookupKey);
pw.println(BTitle+" ID="+id1+" LOOKUP_KEY="+lookupKey);
pw.println(BURL);
pw.println(BBOOKMARK);
pw.println(BVISITS);
}
// Flush the PrintWriter to ensure everything pending is output before closing
pw.flush();
pw.close();
f.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
Log.i("MEDIA", "*************** File not found. Did you" +
" add a WRITE_EXTERNAL_STORAGE permission to the manifest file? ");
} catch (IOException e) {
e.printStackTrace();
}
}
}

Your selection column is wrong in the second query of URI1 the line should read:
Cursor bCur = cr.query(URI1, null, ID1+"=?", new String[]{id1}, null);
The second query isn't needed though. You are first quering all columns, extracting the ID from all columns. And then doing a nest query on the all columns for a particular id.
You don't need the nest query, the first query will have all the data you need.
The question of the best file format. That would depend on what is going to consume the data you are creating.

Related

"Attempted to access a cursor after it has been closed"

I cannot understand this problem. I have tried to get the solution from other stackoverflow questions but failed to get the solution. I was not getting this error before. However, now I don't know what's wrong with the code stated below. Please help me with this. I'm stuck with this problem.
public void getAlbumsLists() {
final Uri uri = MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;
final String _id = MediaStore.Audio.Albums._ID;
final String album = MediaStore.Audio.Media.ALBUM;
final String album_name = MediaStore.Audio.AlbumColumns.ALBUM;
final String artist = MediaStore.Audio.AlbumColumns.ARTIST;
final String tracks = MediaStore.Audio.AlbumColumns.NUMBER_OF_SONGS;
// final String data=MediaStore.Audio.Albums.ALBUM_ID; // NO
// final String id1=MediaStore.Audio.Albums.ALBUM_ID;
final String tit = MediaStore.Audio.Albums.ALBUM; //NO
final String nam = MediaStore.Audio.Albums.ALBUM_KEY; // NO
final String typ = MediaStore.Audio.Media.MIME_TYPE; // NO
final String art = MediaStore.Audio.Albums.ALBUM_ART; //<<<< CAN GET
final String artstkey = MediaStore.Audio.Artists.ARTIST_KEY; //<<<< CAN GET
final String frstyr = MediaStore.Audio.AlbumColumns.FIRST_YEAR; //<<<< CAN GET
final String lstyr = MediaStore.Audio.AlbumColumns.LAST_YEAR; //<<<< CAN GET
final String artstid = "artist_id"; //<<<< CAN GET
final String[] columns = {"*"};
Cursor cursor = getActivity().getContentResolver().query(uri, columns, null, null, null);
// Lists the columns in the cursor
for (String s : cursor.getColumnNames()) {
Log.d("COLUMNS", "Column = " + s);
}
while (cursor.moveToNext()) {
String id = (cursor.getString(cursor.getColumnIndex(_id)));
String name = cursor.getString(cursor.getColumnIndex(album_name));
String artist2 = cursor.getString(cursor.getColumnIndex(artist));
String nr = cursor.getString(cursor.getColumnIndex(tracks));
String x = (cursor.getString(cursor.getColumnIndex(album)));
//String data1=cursor.getString(cursor.getColumnIndexOrThrow(data)); //<<<< NOT A COLUMN
// String id2=cursor.getString(cursor.getColumnIndex(data));
//String title=cursor.getString(cursor.getColumnIndex(tit)); //<<<< NOT A COLUMN
//String name1=cursor.getString(cursor.getColumnIndex(nam)); //<<<< NOT A COLUMN
//String type=cursor.getString(cursor.getColumnIndex(typ)); //<<<< NOT A COLUMN
// AVAIALABLE COLUMNS
String artwork = cursor.getString(cursor.getColumnIndex(art)); //<<<< ADDED
String artistkey = cursor.getString(cursor.getColumnIndex(artstkey)); //<<<< ADDED
String artistid = cursor.getString(cursor.getColumnIndex(artstid)); //<<<< ADDED
String minyear = cursor.getString(cursor.getColumnIndex(frstyr));
String maxyear = cursor.getString(cursor.getColumnIndex(lstyr));
s = new albumInfo(id, name, artist2, nr, artwork, x); // EXCLUDED
albumList.add(s);
cursor.close();
recyclerView1.setAdapter(albumAdapter); // EXCLUDED
}
}
Move cursor.close(); outside the while loop.
Edited..
First time after the while loop iterates the cursor is closed so you need to put it outside while.
I can suggest three improvement scenarios.
You need to move the cursor to first before accessing it for fetching data from the cursor.
You need to close the cursor when nothing is returned or the cursor is null.
Move the cursor close outside of your while loop.
So I would like to recommend you to rewrite the code like following.
// After you have fetched the data from cursor
if(cursor == null) return;
if(cursor.size() == 0) {
cursor.close();
return;
}
for (String s : cursor.getColumnNames()) {
Log.d("COLUMNS", "Column = " + s);
}
// Move the cursor to the first position.
cursor.moveToFirst();
do {
String id = (cursor.getString(cursor.getColumnIndex(_id)));
String name = cursor.getString(cursor.getColumnIndex(album_name));
String artist2 = cursor.getString(cursor.getColumnIndex(artist));
String nr = cursor.getString(cursor.getColumnIndex(tracks));
String x = (cursor.getString(cursor.getColumnIndex(album)));
// ... Other code
} while (cursor.moveToNext());
recyclerView1.setAdapter(albumAdapter); // EXCLUDED
// Move the cursor outside of while loop
cursor.close();

Writing custom Content Provider for photos on phone (part 2)

I'm working on a custom Content Provider for my app. This is part of a course I'm taking on Android apps, so please don't expect the rationale for doing all this to be too great ;-) The whole point here is for me to learn about CP.
I've got a previous post which goes on and on with this, but I think I've managed to simplify my problem quite a bit. So, I'm working on a "gallery app". Since I don't know how and where thumbnail images are stored on the phone, I've decided to simply use MediaStore.Images.Thumbnails to access the thumbs, and show them in my GridView.
However, to fullfill the requirements of said course, I'll write a "PhotoProvider" to load single photos, full screen, in the DetailActivity. Also, for this to make some sense, and not just be a wrapper around MediaStore.Media.Images, I'm extending that data with some EXIF tags from the JPEG file.
Having found a great post here on StackOverflow, and continuing to wrangle the source code provided in class, I've come up with the following classes. Maybe you want to take a look, and help me out (point me in the right direction)?
The URIs I'll support, are context://AUTH/photo/#, to get a specific image (given an IMAGE_ID, from the thumbnail). Furthermore, to make it a bit more interesting, I also want to be able to write data to the EXIF tag UserComment: context://AUTH/photo/#/comment/* (the comment String is the last parameter there). Does that look sensible?
Some questions: (updated)
getType() is confusing. Again, this is lending from the app course. When returning an image, I guess the type should always be image/jpeg (or maybe PNG)?
Edit: Learning more stuff, I now understand that the URI returned from the insert() method is, I guess, optional, but it often useful to return a "link" (i.e. URI) to the new (inserted) data! For my case, after updating the EXIF tags, I could return either null, or an URI to the edited photo: context://AUTH/photo/7271 (where 7271 mimicks the PHOTO_ID).
Below is my (unfinished!) code. Please have a look, especially at the query() and insert() functions :-)
PhotoContract.java
package com.example.android.galleri.app.data;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.MediaStore;
public class PhotoContract {
public static final String CONTENT_AUTHORITY = "no.myapp.android.galleri.app";
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
public static final String PATH_PHOTO = "photo";
public static final String PATH_COMMENT = "comment";
public static final class PhotoEntry {
public static final Uri CONTENT_URI =
BASE_CONTENT_URI.buildUpon().appendPath(PATH_PHOTO).build();
public static final String COLUMN_DISPLAY_NAME = MediaStore.Images.Media.DISPLAY_NAME;
public static final String COLUMN_DATA = MediaStore.Images.Media.DATA;
public static final String COLUMN_DESC = MediaStore.Images.Media.DESCRIPTION;
public static final String COLUMN_DATE_TAKEN = MediaStore.Images.Media.DATE_TAKEN;
public static final String COLUMN_DATE_ADDED = MediaStore.Images.Media.DATE_ADDED;
public static final String COLUMN_TITLE = MediaStore.Images.Media.TITLE;
public static final String COLUMN_SIZE = MediaStore.Images.Media.SIZE;
public static final String COLUMN_ORIENTATION = MediaStore.Images.Media.ORIENTATION;
public static final String COLUMN_EXIF_COMMENT = "UserComment";
public static final String COLUMN_EXIF_AUTHOR = "Author";
// should these simply be image/png??
public static final String CONTENT_TYPE =
ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;
public static final String CONTENT_ITEM_TYPE =
ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;
// makes an URI to a specific image_id
public static final Uri buildPhotoWithId(Long photo_id) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(photo_id)).build();
}
// since we will "redirect" the URI towards MediaStore, we need to be able to extract IMAGE_ID
public static Long getImageIdFromUri(Uri uri) {
return Long.parseLong(uri.getPathSegments().get(1)); // TODO: is it position 1??
}
// get comment to set in EXIF tag
public static String getCommentFromUri(Uri uri) {
return uri.getPathSegments().get(2); // TODO: is it position 2??
}
}
}
PhotoProvider.java:
package com.example.android.galleri.app.data;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.v4.content.CursorLoader;
import android.util.Log;
import java.io.IOException;
public class PhotoProvider extends ContentProvider {
// The URI Matcher used by this content provider.
private static final UriMatcher sUriMatcher = buildUriMatcher();
static final int PHOTO = 100;
static final int PHOTO_SET_COMMENT = 200;
static UriMatcher buildUriMatcher() {
// 1) The code passed into the constructor represents the code to return for the root
// URI. It's common to use NO_MATCH as the code for this case. Add the constructor below.
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
final String authority = PhotoContract.CONTENT_AUTHORITY;
// 2) Use the addURI function to match each of the types. Use the constants from
// WeatherContract to help define the types to the UriMatcher.
// matches photo/<any number> meaning any photo ID
matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#", PHOTO);
// matches photo/<photo id>/comment/<any comment>
matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#/" + PhotoContract.PATH_COMMENT + "/*", PHOTO_SET_COMMENT);
// 3) Return the new matcher!
return matcher;
}
#Override
public String getType(Uri uri) {
// Use the Uri Matcher to determine what kind of URI this is.
final int match = sUriMatcher.match(uri);
switch (match) {
case PHOTO_SET_COMMENT:
return PhotoContract.PhotoEntry.CONTENT_TYPE;
case PHOTO:
return PhotoContract.PhotoEntry.CONTENT_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
#Override
public boolean onCreate() {
return true; // enough?
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
MatrixCursor retCursor = new MatrixCursor(projection);
// open the specified image through the MediaStore to get base columns
// then open image file through ExifInterface to get detail columns
Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);
//Uri baseUri = Uri.parse("content://media/external/images/media");
Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);
String[] MS_projection = {
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATA,
MediaStore.Images.Media.DESCRIPTION,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media.DATE_ADDED,
MediaStore.Images.Media.TITLE,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media.ORIENTATION};
// http://androidsnippets.com/get-file-path-of-gallery-image
Cursor c = getContext().getContentResolver().query(baseUri, MS_projection, null, null, null);
// dump fields (the ones we want -- assuming for now we want ALL fields, in SAME ORDER) into MatrixCursor
Object[] row = new Object[projection.length];
row[0] = c.getString(0); // DISPLAY_NAME
row[1] = c.getBlob(1); // etc
row[2] = c.getString(2);
row[3] = c.getLong(3);
row[4] = c.getLong(4);
row[5] = c.getString(5);
row[6] = c.getInt(6);
row[7] = c.getInt(7);
// NB! Extra +2 fields, for EXIF data.
try {
ExifInterface exif = new ExifInterface((String)row[1]);
row[8] = exif.getAttribute("UserComment");
row[9] = exif.getAttribute("Author");
} catch (IOException e) {
e.printStackTrace();
}
retCursor.addRow(row);
return retCursor;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
String comment_to_set = PhotoContract.PhotoEntry.getCommentFromUri(uri);
Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);
Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);
// get DATA (path/filename) from MediaStore -- only need that specific piece of information
String[] MS_projection = {MediaStore.Images.Media.DATA};
// http://androidsnippets.com/get-file-path-of-gallery-image
Cursor c = getContext().getContentResolver().query(baseUri, MS_projection, null, null, null);
String thumbData = c.getString(0);
try {
ExifInterface exif = new ExifInterface(thumbData);
exif.setAttribute("UserComment", comment_to_set);
exif.saveAttributes();
} catch (IOException e) {
e.printStackTrace();
}
return PhotoContract.PhotoEntry.buildPhotoWithId(IMAGE_ID); // return URI to this specific image
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}
I believe I managed to figure this out, and it wasn't so mysterious after all. Writing a custom content, in this case, provider really boiled down to implementing my own query and update methods. I didn't need any insert or delete, since I'm only reading the MediaStore.
To get started, I designed two URIs; one for query and one for update. The query URI followed the pattern, content://AUTH/photo/#, where # is the IMAGE_ID from MediaStore.Images.Thumbnails. This method essentially runs another query against MediaStore.Images.Media, to get the "standard" data on the specific image, but then gets some additional meta data on the image via the ExifInterface. All this is returned in a MatrixCursor.
The update URI matches the pattern, content://AUTH/photo/#/comment, which then sets (writes) the UserComment EXIF tag in the file with that ID -- again via the ExifInterface.
To answer my question, I'm posting updated code from my PhotoContract and PhotoProvider classes. Essentially, if you're writing a content provider which does not interface the SQLite database -- but something else on the device -- all you need to do is implement those operations in the appropriate methods in your provider class.
PhotoContract
import android.content.ContentResolver;
import android.content.ContentUris;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.MediaStore;
public class PhotoContract {
public static final String CONTENT_AUTHORITY = "com.example.android.myFunkyApp.app";
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
public static final String PATH_PHOTO = "photo";
public static final String PATH_COMMENT = "comment";
//public static final String PATH_AUTHOR = "author";
public static final class ThumbEntry {
public static final String COLUMN_THUMB_ID = MediaStore.Images.Thumbnails._ID;
public static final String COLUMN_DATA = MediaStore.Images.Thumbnails.DATA;
public static final String COLUMN_IMAGE_ID = MediaStore.Images.Thumbnails.IMAGE_ID;
}
public static final class PhotoEntry {
public static final Uri CONTENT_URI =
BASE_CONTENT_URI.buildUpon().appendPath(PATH_PHOTO).build();
public static final String COLUMN_IMAGE_ID = MediaStore.Images.Media._ID;
public static final String COLUMN_DISPLAY_NAME = MediaStore.Images.Media.DISPLAY_NAME;
public static final String COLUMN_DATA = MediaStore.Images.Media.DATA;
public static final String COLUMN_DESC = MediaStore.Images.Media.DESCRIPTION;
public static final String COLUMN_DATE_TAKEN = MediaStore.Images.Media.DATE_TAKEN;
public static final String COLUMN_DATE_ADDED = MediaStore.Images.Media.DATE_ADDED;
public static final String COLUMN_TITLE = MediaStore.Images.Media.TITLE;
public static final String COLUMN_SIZE = MediaStore.Images.Media.SIZE;
public static final String COLUMN_ORIENTATION = MediaStore.Images.Media.ORIENTATION;
public static final String COLUMN_EXIF_COMMENT = "UserComment";
//public static final String COLUMN_EXIF_AUTHOR = "Author";
public static final String CONTENT_TYPE =
ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;
public static final String CONTENT_ITEM_TYPE =
ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;
// makes an URI to a specific image_id
public static final Uri buildPhotoUriWithId(Long photo_id) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(photo_id)).build();
}
// since we will "redirect" the URI towards MediaStore, we need to be able to extract IMAGE_ID
public static Long getImageIdFromUri(Uri uri) {
return Long.parseLong(uri.getPathSegments().get(1)); // always in position 1
}
}
}
PhotoProvider
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.AbstractWindowedCursor;
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.CursorWrapper;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.v4.content.CursorLoader;
import android.util.Log;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
public class PhotoProvider extends ContentProvider {
// The URI Matcher used by this content provider.
private static final UriMatcher sUriMatcher = buildUriMatcher();
static final int PHOTO = 100;
static final int PHOTO_COMMENT = 101;
static final int PHOTO_AUTHOR = 102;
static UriMatcher buildUriMatcher() {
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
final String authority = PhotoContract.CONTENT_AUTHORITY;
// matches photo/<any number> meaning any photo ID
matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#", PHOTO);
// matches photo/<photo id>/comment/ (comment text in ContentValues)
matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#/" + PhotoContract.PATH_COMMENT, PHOTO_COMMENT);
// matches photo/<photo id>/author/ (author name in ContentValues)
//matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#/" + PhotoContract.PATH_AUTHOR, PHOTO_AUTHOR);
return matcher;
}
#Override
public String getType(Uri uri) {
// Use the Uri Matcher to determine what kind of URI this is.
final int match = sUriMatcher.match(uri);
// Note: We always return single row of data, so content-type is always "a dir"
switch (match) {
case PHOTO_COMMENT:
return PhotoContract.PhotoEntry.CONTENT_TYPE;
case PHOTO_AUTHOR:
return PhotoContract.PhotoEntry.CONTENT_TYPE;
case PHOTO:
return PhotoContract.PhotoEntry.CONTENT_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
#Override
public boolean onCreate() {
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
MatrixCursor retCursor = new MatrixCursor(projection);
// open the specified image through the MediaStore to get base columns
// then open image file through ExifInterface to get detail columns
Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);
Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);
// http://androidsnippets.com/get-file-path-of-gallery-image
// run query against MediaStore, projection = null means "get all fields"
Cursor c = getContext().getContentResolver().query(baseUri, null, null, null, null);
if (!c.moveToFirst()) {
return null;
}
// match returned fields against projection, and copy into row[]
Object[] row = new Object[projection.length];
int i = 0;
/* // Cursor.getType() Requires API level > 10...
for (String colName : projection) {
int idx = c.getColumnIndex(colName);
if (idx <= 0) return null; // ERROR
int colType = c.getType(idx);
switch (colType) {
case Cursor.FIELD_TYPE_INTEGER: {
row[i++] = c.getLong(idx);
break;
}
case Cursor.FIELD_TYPE_FLOAT: {
row[i++] = c.getFloat(idx);
break;
}
case Cursor.FIELD_TYPE_STRING: {
row[i++] = c.getString(idx);
break;
}
case Cursor.FIELD_TYPE_BLOB: {
row[i++] = c.getBlob(idx);
break;
}
}
}
*/
//http://stackoverflow.com/questions/11658239/cursor-gettype-for-api-level-11
CursorWrapper cw = (CursorWrapper)c;
Class<?> cursorWrapper = CursorWrapper.class;
Field mCursor = null;
try {
mCursor = cursorWrapper.getDeclaredField("mCursor");
mCursor.setAccessible(true);
AbstractWindowedCursor abstractWindowedCursor = (AbstractWindowedCursor)mCursor.get(cw);
CursorWindow cursorWindow = abstractWindowedCursor.getWindow();
int pos = abstractWindowedCursor.getPosition();
// NB! Expect resulting cursor to contain data in same order as projection!
for (String colName : projection) {
int idx = c.getColumnIndex(colName);
// simple solution: If column name NOT FOUND in MediaStore, assume it's an EXIF tag
// and skip
if (idx >= 0) {
if (cursorWindow.isNull(pos, idx)) {
//Cursor.FIELD_TYPE_NULL
row[i++] = null;
} else if (cursorWindow.isLong(pos, idx)) {
//Cursor.FIELD_TYPE_INTEGER
row[i++] = c.getLong(idx);
} else if (cursorWindow.isFloat(pos, idx)) {
//Cursor.FIELD_TYPE_FLOAT
row[i++] = c.getFloat(idx);
} else if (cursorWindow.isString(pos, idx)) {
//Cursor.FIELD_TYPE_STRING
row[i++] = c.getString(idx);
} else if (cursorWindow.isBlob(pos, idx)) {
//Cursor.FIELD_TYPE_BLOB
row[i++] = c.getBlob(idx);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
// have now handled the first i fields in projection. If there are any more, we expect
// these to be valid EXIF tags. Should obviously make this more robust...
try {
ExifInterface exif = new ExifInterface((String) row[2]);
while (i < projection.length) {
row[i] = exif.getAttribute("UserComment"); //projection[i]); // String (or null)
i++;
}
} catch (IOException e) {
e.printStackTrace();
}
retCursor.addRow(row);
return retCursor;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// URI identifies IMAGE_ID and which EXIF tag to set; content://AUTH/photo/<image_id>/comment or /author
// first, get IMAGE_ID and prepare URI (to get file path from MediaStore)
Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);
Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);
// get DATA (path/filename) from MediaStore -- only need that specific piece of information
String[] MS_projection = {MediaStore.Images.Media.DATA};
// http://androidsnippets.com/get-file-path-of-gallery-image
Cursor c = getContext().getContentResolver().query(baseUri, MS_projection, null, null, null);
if (!c.moveToFirst()) return -1; // can't get image path/filename...
String thumbData = c.getString(0);
// then, use URIMatcher to identify the "operation" (i.e., which tag to set)
final int match = sUriMatcher.match(uri);
String EXIF_tag;
switch (match) {
case PHOTO_COMMENT: {
EXIF_tag = "UserComment";
break;
}
case PHOTO_AUTHOR: {
EXIF_tag = "Author";
break;
}
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
if (EXIF_tag == null) return -1;
// finally, set tag in image
try {
ExifInterface exif = new ExifInterface(thumbData);
String textToSet = values.get(EXIF_tag).toString();
if (textToSet == null)
throw new IOException("Can't retrieve text to set from ContentValues, on key = " + EXIF_tag);
if (textToSet.length() > 48)
throw new IOException("Data too long (" + textToSet.length() + "), on key = " + EXIF_tag);
exif.setAttribute(EXIF_tag, textToSet);
exif.saveAttributes();
} catch (IOException e) {
e.printStackTrace();
}
return 1; // 1 image updated
}
}

Attach a progress bar to an android activity

Hi i have the following code used to read contacts from an andorid phone and write them to a file - i am trying to show the progress of the process below is the code for reading and writing the contacts
package com.lightcone.readcontacts;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import android.app.Activity;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.ContactsContract;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
public class ReadContacts extends Activity {
// To suppress notational clutter and make structure clearer, define some shorthand constants.
private static final Uri URI = ContactsContract.Contacts.CONTENT_URI;
private static final Uri PURI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
private static final String ID = ContactsContract.Contacts._ID;
private static final String DNAME = ContactsContract.Contacts.DISPLAY_NAME;
private static final String HPN = ContactsContract.Contacts.HAS_PHONE_NUMBER;
private static final String CID = ContactsContract.CommonDataKinds.Phone.CONTACT_ID;
private static final String PNUM = ContactsContract.CommonDataKinds.Phone.NUMBER;
private static final String PHONETYPE = ContactsContract.CommonDataKinds.Phone.TYPE;
private String id;
private String name;
private String ph[];
private String phType[];
private File root;
private int phcounter;
private TextView tv;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tv = (TextView) findViewById(R.id.TextView01);
// Allow for up to 9 email and phone entries for a contact
ph = new String[9];
phType = new String[9];
// Check that external media available and writable
checkExternalMedia();
ContentResolver ctr = getContentResolver();
Cursor cur = ctr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
int numberOfContacts = cur.getCount();
Toast.makeText(this, "Reading" +String.valueOf(numberOfContacts)+ "Contacts", Toast.LENGTH_LONG).show();
File dir = new File (root.getAbsolutePath() + "/download");
dir.mkdirs();
File file = new File(dir, "phoneData.txt");
tv.append("Wrote " +numberOfContacts+" to "+file+"\nfor following contacts:\n");
try{
FileOutputStream f = new FileOutputStream(file);
PrintWriter pw = new PrintWriter(f);
ContentResolver cr = getContentResolver();
Cursor cu = cr.query(URI, null, null, null, null);
if (cu.getCount() > 0) {
// Loop over all contacts
while (cu.moveToNext()) {
id = cu.getString(cu.getColumnIndex(ID));
name = cu.getString(cu.getColumnIndex(DNAME));
tv.append("\n"+id+" "+name);
phcounter = 0;
if (Integer.parseInt(cu.getString(cu.getColumnIndex(HPN))) > 0) {
Cursor pCur = cr.query(PURI, null, CID + " = ?", new String[]{id}, null);
while (pCur.moveToNext()) {
ph[phcounter] = pCur.getString(pCur.getColumnIndex(PNUM));
phType[phcounter] = pCur.getString(pCur.getColumnIndex(PHONETYPE));
phcounter ++;
}
pCur.close();
}
// Write identifiers for this contact to the SD card file
pw.println("<nc>"+name);
for(int i=0; i<phcounter; i++){
pw.println("\tphone="+ ph[i]);
}
}
}
// Flush the PrintWriter to ensure everything pending is output before closing
pw.flush();
pw.close();
f.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
Log.i("MEDIA", "*************** File not found. Did you" +
" add a WRITE_EXTERNAL_STORAGE permission to the manifest file? ");
} catch (IOException e) {
e.printStackTrace();
}
}
private void checkExternalMedia () {
boolean mExternalStorageAvailable = false;
boolean mExternalStorageWriteable = false;
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
// We can read and write the media
mExternalStorageAvailable = mExternalStorageWriteable = true;
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
// We can only read the media
mExternalStorageAvailable = true;
mExternalStorageWriteable = false;
} else {
// Can't read or write
mExternalStorageAvailable = mExternalStorageWriteable = false;
}
root = android.os.Environment.getExternalStorageDirectory();
tv.append( "External storage: Exists="+mExternalStorageAvailable+", Writable="
+mExternalStorageWriteable+" Root="+root+"\n");
}
private String getPhoneType(String index){
if(index.trim().equals( "1")){
return "home";
} else if (index.trim().equals("2")){
return "mobile";
} else if (index.trim().equals("3")){
return "work";
} else if (index.trim().equals("7")){
return "other";
} else {
return "?";
}
}
}
use AsyncTask to write the contacts to file and show the progress bar in onPreExecute() method of AsyncTask and dismiss the progress in onPostExecute() method of AsyncTask.

How to either upload or send a txt file after it has been stored to a sd card

I am currently working on my first android app for my comp prog class. The app records contacts onto a txt file and stores it on the SD card. I am able to retrieve the file manually, but I now want the app to send the txt file back to me automatically after it has been created. Please, what is the easiest way to do this. I use eclipse. It doesn't matter how the txt file gets to me, it just needs to...that is the point of the lesson. This works fine and all permissions and stuff are in order. I need to know what to put to send and where. Any additional permissions? I appreciate any help. Thanks in advance. Kevin
package com.lightcone.readcontacts;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import android.app.Activity;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.ContactsContract;
import android.util.Log;
import android.widget.TextView;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
public class ReadContacts extends Activity {
private static final Uri URI = ContactsContract.Contacts.CONTENT_URI;
private static final Uri PURI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
private static final Uri EURI = ContactsContract.CommonDataKinds.Email.CONTENT_URI;
private static final Uri AURI = ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_URI;
private static final String ID = ContactsContract.Contacts._ID;
private static final String DNAME = ContactsContract.Contacts.DISPLAY_NAME;
private static final String HPN = ContactsContract.Contacts.HAS_PHONE_NUMBER;
private static final String LOOKY = ContactsContract.Contacts.LOOKUP_KEY;
private static final String CID = ContactsContract.CommonDataKinds.Phone.CONTACT_ID;
private static final String EID = ContactsContract.CommonDataKinds.Email.CONTACT_ID;
private static final String AID = ContactsContract.CommonDataKinds.StructuredPostal.CONTACT_ID;
private static final String PNUM = ContactsContract.CommonDataKinds.Phone.NUMBER;
private static final String PHONETYPE = ContactsContract.CommonDataKinds.Phone.TYPE;
private static final String EMAIL = ContactsContract.CommonDataKinds.Email.DATA;
private static final String EMAILTYPE = ContactsContract.CommonDataKinds.Email.TYPE;
private static final String STREET = ContactsContract.CommonDataKinds.StructuredPostal.STREET;
private static final String CITY = ContactsContract.CommonDataKinds.StructuredPostal.CITY;
private static final String STATE = ContactsContract.CommonDataKinds.StructuredPostal.REGION;
private static final String POSTCODE = ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE;
private static final String COUNTRY = ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY;
private String id;
private String lookupKey;
private String name;
private String street;
private String city;
private String state;
private String postcode;
private String country;
private String ph[];
private String phType[];
private String em[];
private String emType[];
private File root;
private int emcounter;
private int phcounter;
private int addcounter;
private TextView tv;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tv = (TextView) findViewById(R.id.TextView01);
em = new String[5];
emType = new String[5];
ph = new String[5];
phType = new String[5];
checkExternalMedia();
File dir = new File (root.getAbsolutePath() + "/download");
dir.mkdirs();
File file = new File(dir, "phoneData.txt");
tv.append("Wrote " +file+"\nfor following contacts:\n");
try{
FileOutputStream f = new FileOutputStream(file);
PrintWriter pw = new PrintWriter(f);
ContentResolver cr = getContentResolver();
Cursor cu = cr.query(URI, null, null, null, null);
if (cu.getCount() > 0) {
while (cu.moveToNext()) {
street = "";
city = "";
state = "";
postcode = "";
country = "";
id = cu.getString(cu.getColumnIndex(ID));
name = cu.getString(cu.getColumnIndex(DNAME));
lookupKey = cu.getString(cu.getColumnIndex(LOOKY));
tv.append("\n"+id+" "+name);
phcounter = 0;
if (Integer.parseInt(cu.getString(cu.getColumnIndex(HPN))) > 0) {
Cursor pCur = cr.query(PURI, null, CID + " = ?", new String[]{id}, null);
while (pCur.moveToNext()) {
ph[phcounter] = pCur.getString(pCur.getColumnIndex(PNUM));
phType[phcounter] = pCur.getString(pCur.getColumnIndex(PHONETYPE));
phcounter ++;
}
pCur.close();
}
emcounter = 0;
Cursor emailCur = cr.query(EURI, null, EID + " = ?", new String[]{id}, null);
while (emailCur.moveToNext()) {
em[emcounter] = emailCur.getString(emailCur.getColumnIndex(EMAIL));
emType[emcounter] = emailCur.getString(emailCur.getColumnIndex(EMAILTYPE));
emcounter ++;
}
emailCur.close();
addcounter = 0;
Cursor addCur = cr.query(AURI, null, AID + " = ?", new String[]{id}, null);
while (addCur.moveToNext()) {
street = addCur.getString(addCur.getColumnIndex(STREET));
city = addCur.getString(addCur.getColumnIndex(CITY));
state = addCur.getString(addCur.getColumnIndex(STATE));
postcode = addCur.getString(addCur.getColumnIndex(POSTCODE));
country = addCur.getString(addCur.getColumnIndex(COUNTRY));
addcounter ++;
}
addCur.close();
pw.println(name+" ID="+id+" LOOKUP_KEY="+lookupKey);
for(int i=0; i<phcounter; i++){
pw.println(" phone="+ ph[i]+" type="+phType[i] + " ("
+ getPhoneType(phType[i]) + ") ");
}
for(int i=0; i<emcounter; i++){
pw.println(" email="+ em[i]+" type="+emType[i] + " ("
+ getEmailType(emType[i]) + ") ");
}
if(addcounter > 0){
if(street != null) pw.println(" street="+street);
if(city != null) pw.println(" city="+city);
if(state != null) pw.println(" state/region="+state);
if(postcode != null) pw.println(" postcode="+postcode);
if(country != null) pw.println(" country="+country);
}
}
}
pw.flush();
pw.close();
f.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
Log.i("MEDIA", "*************** File not found. Did you" +
" add a WRITE_EXTERNAL_STORAGE permission to the manifest file? ");
} catch (IOException e) {
e.printStackTrace();
}
}
private void checkExternalMedia () {
boolean mExternalStorageAvailable = false;
boolean mExternalStorageWriteable = false;
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
mExternalStorageAvailable = mExternalStorageWriteable = true;
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
mExternalStorageAvailable = true;
mExternalStorageWriteable = false;
} else {
mExternalStorageAvailable = mExternalStorageWriteable = false;
}
root = android.os.Environment.getExternalStorageDirectory();
tv.append( "External storage: Exists="+mExternalStorageAvailable+", Writable="
+mExternalStorageWriteable+" Root="+root+"\n");
}
private String getPhoneType(String index){
if(index.trim().equals( "1")){
return "home";
} else if (index.trim().equals("2")){
return "mobile";
} else if (index.trim().equals("3")){
return "work";
} else if (index.trim().equals("7")){
return "other";
} else {
return "?";
}
}
private String getEmailType(String index){
if(index.trim().equals( "1")){
return "home";
} else if (index.trim().equals("2")){
return "work";
} else if (index.trim().equals("3")){
return "other";
} else if (index.trim().equals("4")){
return "mobile";
} else {
return "?";
}
}
}

How do I get a Uri to an image in my Assets that will work for the SearchManager.SUGGEST_COLUMN_ICON_1 column?

I have successfully integrated my app's country search into the global search facility and now I am trying to display each country's flag next to the search suggestions. Search inside my app works this way but of course I have control of the list and its view binding myself. So I know the flags are all there and I can use them in the rest of my app.
The trouble comes when I try to supply a Uri to a .gif file in my Assets. According to the search documentation the value of the column with the key SearchManager.SUGGEST_COLUMN_ICON_1 should be a Uri to the image.
Below is what the code looks like. In response to the ContentProvider method public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) I am creating a MatrixCursor that maps columns from my country database to those required by the search facility. The country names show up fine and I can select them and correctly respond in my application.
I have tried forming the Uri three different ways:
// String flagUri = "file:///android_asset/" + flagPath;
// String flagUri = "file:///assets/" + flagPath;
String flagUri = "android.resource://com.lesliesoftware.worldinfo.WorldInfoActivity/assets/" + flagPath;
columnValues.add (flagUri);
They all lead to the same thing - my application icon next to each suggestion which I can get by using a value of empty string.
Is there a Uri that will work? How can I get the country flag icon next to the search suggestions?
Thanks Ian
The full source:
private Cursor search (String query, int limit) {
query = query.toLowerCase ();
String[] requestedColumns = new String[] {
BaseColumns._ID,
SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_ICON_1,
};
String[] queryColumns = new String[] {
WorldInfoDatabaseAdapter.KEY_ROWID,
WorldInfoDatabaseAdapter.KEY_COUNTRYNAME,
WorldInfoDatabaseAdapter.KEY_COUNTRYCODE
};
return packageResults (query, requestedColumns, queryColumns, limit);
}
private Cursor packageResults (String query, String[] requestedColumns, String[] queryMappedColumns, int limit) {
if (requestedColumns.length != queryMappedColumns.length)
throw new IllegalArgumentException ("Internal error: requested columns do not map to query columns");
MatrixCursor results = new MatrixCursor (requestedColumns);
// Query the country list returns columns: KEY_ROWID, KEY_COUNTRYNAME, KEY_COUNTRYCODE
Cursor dbResults = myDbHelper.getCountryList (query);
// Verify that the query columns are available
for (int index = 0; index < queryMappedColumns.length; index++) {
int col = dbResults.getColumnIndex (queryMappedColumns[index]);
if (col == -1)
throw new IllegalArgumentException ("Internal error: requested column '" +
queryMappedColumns[index] + "' was not returned from the database.");
}
// Loop over the database results building up the requested results
int rowCount = 0;
while (dbResults.moveToNext () && rowCount < limit) {
Vector<String> columnValues = new Vector<String> ();
for (int index = 0; index < requestedColumns.length; index++) {
if (requestedColumns[index].compareTo (SearchManager.SUGGEST_COLUMN_ICON_1) == 0) {
String flagPath = "flags/small/" + dbResults.getString (
dbResults.getColumnIndexOrThrow (queryMappedColumns[index]))
+ "-flag.gif";
// String flagUri = "file:///android_asset/" + flagPath;
// String flagUri = "file:///assets/" + flagPath;
String flagUri = "android.resource://com.lesliesoftware.worldinfo.WorldInfoActivity/assets/" + flagPath;
columnValues.add (flagUri);
} else {
// Add the mapped query column values from the database
String colValue = dbResults.getString (dbResults.getColumnIndexOrThrow (queryMappedColumns[index]));
columnValues.add (colValue);
}
}
results.addRow (columnValues);
rowCount++;
}
return results;
}
EDIT:
I have tried other variations including moving the images from the assets to the raw folder. Nothing worked. Here are the uri's I tried:
flagUriStr = "android.resource://com.lesliesoftware.worldinfo/raw/flags/small/" +
countryCode + "-flag.gif";
flagUriStr = "android.resource://com.lesliesoftware.worldinfo/assets/flags/small/" +
countryCode + "-flag.gif";
flagUriStr = "android.resource://com.lesliesoftware.worldinfo/assets/flags/small/" +
countryCode + "-flag";
flagUriStr = "android.resource://com.lesliesoftware.worldinfo/raw/" +
countryCode + "-flag.gif";
flagUriStr = "android.resource://com.lesliesoftware.worldinfo/raw/" +
countryCode + "-flag";
The only uri that did work was if I moved a test flag into my drawable folder:
flagUriStr = "android.resource://com.lesliesoftware.worldinfo/" +
R.drawable.small_ca_flag;
Sadly after much searching around I have reached the conclusion that you cannot return a uri to an image asset to the search facility. What I did instead was move my flag images to the resources (so they do not clutter up my app's resources I created a library for the flag images) and use resource uri's. So, in my provider I have code that looks like this in the loop that maps database results to search results:
if (requestedColumns[index].compareTo (SearchManager.SUGGEST_COLUMN_ICON_1) == 0) {
// Translate the country code into a flag icon uri
String countryCode = dbResults.getString (
dbResults.getColumnIndexOrThrow (queryMappedColumns[index]));
int flagImageID = FlagLookup.smallFlagResourceID (countryCode);
String flagUriStr = "android.resource://com.lesliesoftware.worldinfo/" + flagImageID;
columnValues.add (flagUriStr);
}
and look up code that looks like this:
static public int smallFlagResourceID (String countryCode) {
if (countryCode == null || countryCode.length () == 0)
return R.drawable.flag_small_none;
if (countryCode.equalsIgnoreCase ("aa"))
return R.drawable.flag_small_aa;
else if (countryCode.equalsIgnoreCase ("ac"))
return R.drawable.flag_small_ac;
else if (countryCode.equalsIgnoreCase ("ae"))
return R.drawable.flag_small_ae;
...
I will just have to make sure I create a test that verifies that all countries that have flags returns the expected results.
You first create an AssetProvider. We will later create uris that will be handled by this class.
public class AssetsProvider extends ContentProvider {
private AssetManager assetManager;
public static final Uri CONTENT_URI =
Uri.parse("content://com.example.assets");
#Override
public int delete(Uri arg0, String arg1, String[] arg2) { return 0; }
#Override
public String getType(Uri uri) { return null; }
#Override
public Uri insert(Uri uri, ContentValues values) { return null; }
#Override
public boolean onCreate() {
assetManager = getContext().getAssets();
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; }
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; }
#Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
String path = uri.getPath().substring(1);
try {
AssetFileDescriptor afd = assetManager.openFd(path);
return afd;
} catch (IOException e) {
throw new FileNotFoundException("No asset found: " + uri, e);
}
}
}
Lot's of boilerplate in there. The essential method is of course the openAssetFile that justs translates the path of the uri passed to it into a AssetFileDescriptor. In order for Android to be able to pass URIs to this provider, remember to include this in your AndroidManifest.xml file inside the application-tag:
<provider
android:name=".AssetsProvider"
android:authorities="com.example.assets" />
Now, in your search provider you can create an URI like this:
content://com.example.assets/my_folder_inside_assets/myfile.png
I was able to refer images from drawable in SearchRecentSuggestionsProvider using following uri,
"android.resource://your.package.here/drawable/image_name
Just wanted to add to vidstige's answer: I wanted to serve files that I had downloaded to my temporary cache directory earlier, so I can serve external image thumbnails to the SearchView suggestions from URLs in my search ContentProvider. I used this function instead:
#Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
String filename = uri.getLastPathSegment();
try {
File file = new File(getContext().getCacheDir(), filename);
// image downloading has to be done in the search results provider, since it's not on the UI thread like this guy.
//downloadAndSaveFile("http://urdomain/urfile.png", filename);
AssetFileDescriptor afd = new AssetFileDescriptor(
ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY | ParcelFileDescriptor.MODE_WORLD_READABLE),
0, AssetFileDescriptor.UNKNOWN_LENGTH);
return afd;
} catch (IOException e) {
throw new FileNotFoundException("No asset found: " + uri);
}
}

Categories

Resources