Friday, September 27, 2013

OpenGL - load 3D objects from .obj files


In this article I will show you, how to load  complex 3D objects into your JAVA program and how to render them. The example uses JAVA/JOGL, so it is not directly Android-related.

If you try to create some 3D program based on the OpenGL platform, you probably have learned, that it is nearly impossible to create the 3D objects in a vertex-by-vertex way. Maybe there are geeks who are able to define, say 10 kilos of triangles by hand, but I'm not so assiduous to break my fingers on a keyboard.

Fortunately there are much more convenient ways of creating 3D objects. The most convenient of all is probably to download them from Internet ;-) But before we start to use them in the program we have to load them there.
3D objects loaded into the JAVA program

The way I'm about to teach you is that simple:
  • Download 3D object from Internet
  • Import it to the Blender program (http://www.blender.org/)
  • Export it to the .obj format
  • Load it to your program and use it there
Of course you can create the 3D object with Blender (or another 3D editor) by yourself.

The last part of the list can be done surprisingly easily cause the .obj format (http://en.wikipedia.org/wiki/Wavefront_.obj_file) is quite simple and straightforward. I have written the simple loader for you and you are welcome to download it here. Don't worry about the size of the archive - it is so big because it holds four example models for you, so you can just load the projects into Netbeans and run. There are two projects in the archive
  • JOGLObj - the loader itself with the "main" class JOGLObject
  • JOGL - just the example program which shows how to use the JOGLObject

Note you will need java JDK7, and the JOGL library installed and imported to the project (http://jogamp.org/wiki/index.php/Downloading_and_installing_JOGL)


Hope this will help you to create something nice.

Thursday, January 24, 2013

Copy files between Android and PC


In this article I will show you, how you can copy files between PC and an Android device.

If you use some Android device, especially if you are a Linux user, you have probably learned, that it is not any easy, to put some files  from PC on the Android, or copy some files from the Android on your PC.

In this mini 'promotional' article I will introduce you an application, which allows you to copy files between any PC and Android. The application is named AndroidRC (or AndroidRCServer, which is actually an alias). You can install the application from Google Play.

Once it is installed on your Android device, the only thing you have to do is to connect to the server with your web browser. The server will refuse your connection as you have to approve the access for your PC's IP address. So enable the IP on your Android and reload the page in the browser. Now you can work with files on your Android. you can do much more in fact. See the video below.




Hope this will help you.

Monday, January 21, 2013

Android - print all contacts properties


In this article I will show you, how you can print out all the contacts from an Android device.

If you are trying to work with contacts on Android, you have probably learned, that the contacts structure is not any simple there. As a matter of fact it is not any difficult, anyway it may be challenging to figure out, how to work with all the database stuff properly, especially on older versions (<11) of the Android's API.

In short: Contacts are stored in three tables - Contacts, RawContacts and Data. Every Contacts row can be linked in several RawContacts rows and every RawContacts row can be linked in several Data rows. The links are made with ID's. Easy - isn't it?

I encourage you to read the http://developer.android.com/guide/topics/providers/contacts-provider.html document.

Following snippet shows how to print contacts information on the standard output:
 /**
   * Method wich reads user ID's from Contacts table and calls printRaw for all
   * of them
   */
  private void contacts() {
    // ID is only thing, that we are interested in
    String[] projection = new String[] { ContactsContract.Contacts._ID };
    // Get all user IDs
    Cursor cursor = getContentResolver().query(
        ContactsContract.Contacts.CONTENT_URI, projection, null, null, null);
    if (cursor.moveToFirst()) {
      while (!cursor.isAfterLast()) {
        System.out.println("######################");
        printRaw(cursor.getString(cursor
            .getColumnIndex(ContactsContract.Contacts._ID)));
        cursor.moveToNext();
      }
    }
  }
  
  /**
   * For given user ID finds all rows in RawContacts. Then calls printData for
   * all of them
   * 
   * @param _id
   */
  private void printRaw(String _id) {
    //What we are interested in
    String[] projection = new String[] { ContactsContract.RawContacts._ID,
        ContactsContract.RawContacts.ACCOUNT_TYPE,
        ContactsContract.RawContacts.ACCOUNT_NAME };
    Cursor cursor = getContentResolver().query(
        ContactsContract.RawContacts.CONTENT_URI, projection,
        ContactsContract.RawContacts.CONTACT_ID + " = ?", new String[] { _id },
        null);
    if (cursor.moveToFirst()) {
      while (!cursor.isAfterLast()) {
        System.out.println("======="
            + cursor.getString(cursor
                .getColumnIndex(ContactsContract.RawContacts.ACCOUNT_NAME))
            + "@"
            + cursor.getString(cursor
                .getColumnIndex(ContactsContract.RawContacts.ACCOUNT_TYPE))
            + "=========");
        printData(cursor.getString(cursor
            .getColumnIndex(ContactsContract.RawContacts._ID)));
        cursor.moveToNext();
      }
    }
  }
  /**
   * This one prints all available data for the given RawContact
   * @param _id
   */
  private void printData(String _id) {
    String[] projection = new String[] { ContactsContract.Data.MIMETYPE,
        ContactsContract.Data.DATA1, ContactsContract.Data.DATA2,
        ContactsContract.Data.DATA5, ContactsContract.Data.DATA14,
        ContactsContract.Data.DATA15 };
    Cursor cursor = getContentResolver().query(
        ContactsContract.Data.CONTENT_URI, projection,
        ContactsContract.Data.RAW_CONTACT_ID + " = ?", new String[] { _id },
        null);
    if (cursor.moveToFirst()) {
      while (!cursor.isAfterLast()) {
        String mime = cursor.getString(cursor
            .getColumnIndex(ContactsContract.Data.MIMETYPE));
        if (mime.equals(StructuredName.CONTENT_ITEM_TYPE)) {
          System.out.println("Name: "
              + cursor.getString(cursor
                  .getColumnIndex(StructuredName.DISPLAY_NAME)));
        } else if (mime.equals(Phone.CONTENT_ITEM_TYPE)) {
          System.out.println("Phone: "
              + cursor.getString(cursor.getColumnIndex(Phone.NUMBER)));
        } else if (mime.equals(Email.CONTENT_ITEM_TYPE)) {
          System.out.println("E-mail: "
              + cursor.getString(cursor.getColumnIndex(Email.ADDRESS)));
        } else if (mime.equals(Photo.CONTENT_ITEM_TYPE)) {
          byte[] photo = cursor.getBlob(cursor.getColumnIndex(Photo.PHOTO));
          if (photo != null) {
            System.out.println("Photo: " + photo.length);
          }
        } else if (mime.equals(Organization.CONTENT_ITEM_TYPE)) {
          System.out.println("Organization: "
              + cursor.getString(cursor.getColumnIndex(Organization.COMPANY)));
        } else if (mime.equals(Im.CONTENT_ITEM_TYPE)) {
          System.out.println("Instant messaging: "
              + cursor.getString(cursor.getColumnIndex(Im.PROTOCOL)));
        } else if (mime.equals(Nickname.CONTENT_ITEM_TYPE)) {
          System.out.println("Nick: "
              + cursor.getString(cursor.getColumnIndex(Nickname.NAME)));
        } else if (mime.equals(Note.CONTENT_ITEM_TYPE)) {
          System.out.println("Note: "
              + cursor.getString(cursor.getColumnIndex(Note.NOTE)));
        } else if (mime.equals(StructuredPostal.CONTENT_ITEM_TYPE)) {
          System.out.println("Postal: "
              + cursor.getString(cursor
                  .getColumnIndex(StructuredPostal.FORMATTED_ADDRESS)));
        } else if (mime.equals(GroupMembership.CONTENT_ITEM_TYPE)) {
          System.out.println("Group: "
              + cursor.getString(cursor
                  .getColumnIndex(GroupMembership.GROUP_ROW_ID)));
        } else if (mime.equals(Website.CONTENT_ITEM_TYPE)) {
          System.out.println("Web: "
              + cursor.getString(cursor.getColumnIndex(Website.URL)));
        } else if (mime.equals(Event.CONTENT_ITEM_TYPE)) {
          System.out.println("Event: "
              + cursor.getString(cursor.getColumnIndex(Event.START_DATE))
              + ": " + cursor.getString(cursor.getColumnIndex(Event.TYPE)));
        } else if (mime.equals(Relation.CONTENT_ITEM_TYPE)) {
          System.out.println("Relation: "
              + cursor.getString(cursor.getColumnIndex(Relation.NAME)));
        } else if (mime.equals(SipAddress.CONTENT_ITEM_TYPE)) {
          System.out
              .println("SIP: "
                  + cursor.getString(cursor
                      .getColumnIndex(SipAddress.SIP_ADDRESS)));
        }
        cursor.moveToNext();
      }
    }
  }
Note, that just showed approach follows the contacts structure stupidly and therefore is pretty slow, due to plenty of subsequent database calls. Some more efficient way should be used in a real application - for example list records from the Data table which contains ID's of RawContact same as of the Contact directly in a one shot.

Hope this will help you.

Wednesday, December 19, 2012

Disable text selection in HTML

This is not Android-related, but I do not plan to create a new blog for it:-)
Here is the way how to text selection can be disabled in html. It is usable in case of javascript GUI building.
<body onmousedown="return false">

Tuesday, September 18, 2012

Memory leak when setting Drawable from an AsyncTask

In this article I share my experience with memory leak problem which have relieved me of several hours of my precious time. Maybe I will not tell anything new to you, but maybe I save  someone's minute.

If you are working with ImageView on Android, you probably know, that it is recommended way to update (fill) the ImageView with a Drawable from an AsyncTask, especially in cases that the image for the Drawable have to be downloaded from the Internet first. In the examples below m.photo.getFullSizeImg() represents the method, which downloads the image from Internet and put it into the Drawable object.
During my work I have faced problem with the OutOfMemoryException when I try to start my image -showing-activity several times.

My original, bad one, memory-leak-causing code looked like this:
public class Populator extends AsyncTask<Void, Drawable, Drawable> {
    
    @Override
    protected Drawable doInBackground(Void... params) {
      Drawable preview = null;
      preview = m.photo.getFullSizeImg();
      return preview;
    }
    
    @Override
    protected void onPostExecute(Drawable _preview) {
      imageView.setImageDrawable(_preview);
    }
  }
The problem with this cute code is that variable preview holds the reference to the image Drawable even after onPostExecute() is finished and therefore the garbage collector can't remove the AsyncTask from the memory. So to solve the problem, we have to assign null to the preview variable. that's all and two possible ways of doing it I show bellow.

Well working code using onProgressUpdate() instead of onPostExecute():
public class Populator extends AsyncTask<Void, Drawable, Drawable> {
    
    @Override
    protected Drawable doInBackground(Void... params) {
      Drawable preview = null;
      preview = m.photo.getFullSizeImg();
      publishProgress(preview);
      preview=null;
      return null;
    }
    
    @Override
    protected void onProgressUpdate(Drawable... _preview) {
      imageView.setImageDrawable(_preview[0]);
    }
  }

Another well working code using class variable and onPostExecute():
  public class Populator extends AsyncTask<Void, Void, Void> {
    private Drawable preview = null;
    
    @Override
    protected Void doInBackground(Void... params) {
      preview = m.photo.getFullSizeImg();
      return null;
    }
    
    @Override
    protected void onPostExecute(Void params) {
      imageView.setImageDrawable(preview);
      preview = null;
    }
  }


Hope this will help you.

Tuesday, September 11, 2012

Connect to Picasa album from Android using OAuth2

In this article you will learn how to connect to Picasa service using OAuth2 protocol from Android. This example uses only pure Android API, i.e. no extra jars of libraries are needed.

If you plan to connect to the Picasa albums from your Android application, you probably know, that there is a relatively big number of different "samples" and "guides" which encourages you to use "this protocol" and "that library" saying in turn that this and that is already deprecated and should not be used anymore. Moreover majority of those samples are about different (tasks) services and therefore can not work with Picasa service without adjustments, which are not clear. I have been googling around for about four days as I tried to figure out, what is the proper way of working with Picasa albums, downloading tons of sources, including bags of jars to my "simplesample" project.

Finally I have figured out, that working with Picasa is very easy from Android if you know how to do It.

All we have to do is :

  • Register our application on the Api console https://code.google.com/apis/console/ to get access to the service. Note, that Picasa service is not listed in possible services. It does not matter as you can have 0 active service to work with picasa.
  • Take an OAuth2 authToken,from AccountManager
  • Sign your HttpURLConnection with the authToken


Data that you obtain from Picasa service are in the Atom XML format and therefore there are very simple to parse. For more detailed reference about how to query Picasa see https://developers.google.com/picasa-web/docs/2.0/developers_guide_protocol .

Here goes an activity example. The activity will list albums titles to the LogCat window in Eclipse:

//FIXME This class does not deal with situation when there is not any user account in the device
public class ActivityGallery extends Activity
  implements AccountManagerCallback<Bundle> {
  private final String LOGTAG = getClass().getName();
  /**This is what you have got from the api console https://code.google.com/apis/console/**/
  private final String CLIENT_ID = "123blahblahblah456.com";
  /**
   * Service identification. Note the prefix oauth2 - it is necessary
   * See https://developers.google.com/gdata/faq?hl=cs#AuthScopes
   **/
  private final String AUTH_TOKEN_TYPE = "oauth2:http://picasaweb.google.com/data/";
  /** Type of accounts that we are interested in **/
  private final String ACCOUNT_TYPE = "com.google";
  /** Keys for a settings items - not auth-related stuff ;-) **/
  static final String PREFKEY_ACCOUNT_NAME = "pref1_name";
  static final String PREFKEY_AUTH_TOKEN = "pref2_token";
  private SharedPreferences settings;
  private AccountManager aManager;
  private String accountName;
  private String authToken;
  private HttpURLConnection connection;


  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Inspect preferences for saved values and get AccountManager
    settings = getPreferences(Activity.MODE_PRIVATE);
    // accountName = settings.getString(PREFKEY_ACCOUNT_NAME, null);
    // authToken = settings.getString(PREFKEY_AUTH_TOKEN, null);
    aManager = AccountManager.get(this);
    dance();
  }

  /**
   * Do the authentication dance.
   * At first try to use an account name and an authToken from the
   * SharedPreferences
   * If the first fails, try to get authToken for an accountName from
   * SharedPreferences
   * If the second fails, try to choose an account and get authToken for it.
   */
  public void dance() {
    Log.v(LOGTAG, "dance()");
    // We have got everything
    if (accountName != null && authToken != null) {
      listAlbums(true);
    } else {
      // We have to do more to obtain an authToken
      Account account = null;
      // We have got some account name, let's verify if it is still valid
      if (accountName != null) {
        Account[] accounts = aManager.getAccountsByType(ACCOUNT_TYPE);
        for (Account acc : accounts) {
          if (acc.name.equals(accountName)) {
            account = acc;
            break;
          }
        }
      }
      Bundle options=new Bundle();
      if (account != null) {
        // Well, we don't have an authToken, but we have a valid account
        aManager.getAuthToken(account, AUTH_TOKEN_TYPE, options, this, this,
            null);
      } else {
        // Hmmm, we have to choose an account to use
        Log.v(LOGTAG, "chooseAccount()");
        aManager.getAuthTokenByFeatures(ACCOUNT_TYPE, AUTH_TOKEN_TYPE, null,
            this, options, null, this, null);
      }
    }
  }
  /**
   * Callback method from getAuthTokenByFeatures() and getAuthToken(). All we
   * have to do
   * is remember an authToken and an accountName and then begin to work.
   * 
   * @param future
   */
  @Override
  public void run(AccountManagerFuture<Bundle> future) {
    Log.v(LOGTAG, "AccountManagerCallback.run()");
    try {
      Bundle bundle = future.getResult();
      if (bundle.containsKey(AccountManager.KEY_AUTHTOKEN)) {
        SharedPreferences.Editor editor = settings.edit();
        authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
        Log.v(LOGTAG, "setAuthToken()" + authToken);
        editor.putString(PREFKEY_AUTH_TOKEN, authToken);
        accountName = bundle.getString(AccountManager.KEY_ACCOUNT_NAME);
        Log.v(LOGTAG, "setAccountName()" + accountName);
        editor.putString(PREFKEY_ACCOUNT_NAME, accountName);
        editor.commit();
        listAlbums(true);
      }
    } catch (AuthenticatorException _e) {
      _e.printStackTrace();
      // TODO if the authenticator failed to respond
    } catch (OperationCanceledException _e) {
      _e.printStackTrace();
      // TODO user cancelled the operation
    } catch (IOException _e) {
      _e.printStackTrace();
      // TODO if I/O problem creating a new auth token, usually because of
      // network trouble
    }
  }

  public void listAlbums(boolean _ready) {
    URL url = null;
    try {
      url = new URL("https://picasaweb.google.com/data/feed/api/user/default");
    } catch (MalformedURLException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    try {
      connection = (HttpURLConnection) url.openConnection();
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    connection.addRequestProperty("X-GData-Client", CLIENT_ID);//This may be is not necessary
    connection.setRequestProperty("GData-Version", "2");
    connection.setRequestProperty("Authorization", "OAuth " + authToken);
    try {
      Log.v(LOGTAG, "Response" + connection.getResponseCode() + " ("
          + connection.getResponseMessage() + ")");
      if(connection.getResponseCode()==401 || connection.getResponseCode()==403){
        aManager.invalidateAuthToken(ACCOUNT_TYPE, authToken);
        authToken=null;
        //Dance again to refresh the authToken
        //- it should be treated more carefully to prevent an eternal dance
        dance();
        return;
      }
      //List user's albums
      DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
      try {
        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        Document doc = dBuilder.parse(connection.getInputStream());
        Element root = doc.getDocumentElement();
        NodeList nodes = root.getElementsByTagName("entry");
        for (int i = 0; i < nodes.getLength(); i++) {
          Element element = (Element)nodes.item(i);
          Element title=(Element)element.getElementsByTagName("title").item(0);
          Log.v(LOGTAG, title.getChildNodes().item(0).getNodeValue()+"");
        }
        
      } catch (ParserConfigurationException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      } catch (SAXException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }

    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}

Note, that you will need following permissions declared in yout manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

Hope this will work for you.

Monday, September 3, 2012

Exif metadata handler for Android (pure JAVA)

In this article I will introduce you a single, pure JAVA one-class-based Exif metadata driver, Which allows you to read or modify any information in Exif area of JPEG file.

If you are trying to work with Exif metadata of JPEG images,  you probably know that Android provides native ExifInterface. Sadly this class does not allow you to modify some tags, namely Copyright, Artist, User description, e.t.c. So I have decided to write my own simple Exif driver, which allows me to do anything I need to do with the Exif data.

The work with the Exif driver looks like this:
exifDriver = ExifDriver.getInstance(originalFile.getAbsolutePath());
if (exifDriver != null) {
        exifManager = new ExifManager(exifDriver, mContext);
        // We can modify some info
        exifManager.setArtist("The artist");
        exifManager.setUserComment("User comment ");
        // Save the image - do not use the same one !
        exifDriver.save(modifiedFile.getAbsolutePath());
        // Here we go - read info from the modified Exif file
        exifDriver = ExifDriver.getInstance(modifiedFile.getAbsolutePath());
        if (exifDriver != null) {
          exifManager = new ExifManager(exifDriver, mContext);
          logTag(exifManager.getExifRelated(ExifDriver.TAG_USER_COMMENT));

The complete code of an example activity follows. The activity let's user take image with camera, modifies Artist and User comment tags and then displays all tags, which are set in image. Code looks long, but it is almost because the number of different tags is so high.
public class MainActivity extends Activity {
  private final String LOGTAG = getClass().getName();
  private final int REQUEST_FROM_CAMERA = 1;
  private ProgressDialog progressDialog;
  private File origFile;
  private File modifiedFile;
  private Uri photoUri;
  private ImageView imagePreview;
  private Context mContext;

  /**
   * Usual onCreate - nothing special there
   */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mContext = this;
    setContentView(R.layout.activity_main);
    imagePreview = (ImageView) findViewById(R.id.imagePreview);
  }

  /**
   * Convenience method - test if there is any camera app in the device
   * 
   * @param intent
   * @return
   */
  private boolean isIntentAvailable(Intent intent) {
    final PackageManager mgr = getPackageManager();
    return mgr.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
        .size() > 0;
  }

  /**
   * Button handler. Create intent for camera, create temp file and run intent
   */
  public void handleCamera(View _view) {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (isIntentAvailable(intent)) {
      String storageState = Environment.getExternalStorageState();
      if (storageState.equals(Environment.MEDIA_MOUNTED)) {
        GregorianCalendar today = new GregorianCalendar();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
        origFile = new File(Environment.getExternalStorageDirectory()
            + File.separator + getText(R.string.app_name) + File.separator
            + format.format(today.getTime()) + ".jpg");
        modifiedFile = new File(Environment.getExternalStorageDirectory()
            + File.separator + getText(R.string.app_name) + File.separator
            + format.format(today.getTime()) + "_modif.jpg");
        try {
          if (origFile.exists()) {
            origFile.delete();
          }
          origFile.getParentFile().mkdirs();
          origFile.createNewFile();

        } catch (IOException e) {
          Log.e(LOGTAG, "Could not create file.", e);
        }
        photoUri = Uri.fromFile(origFile);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
        startActivityForResult(intent, REQUEST_FROM_CAMERA);
      } else {
        new AlertDialog.Builder(this)
            .setMessage(
                "External Storeage (SD Card) is required.\n\nCurrent state: "
                    + storageState).setCancelable(true).create().show();
      }
    } else {
      new AlertDialog.Builder(this).setTitle("No camera")
          .setMessage("There is no camera application to take the picture")
          .setPositiveButton("OK", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface arg0, int arg1) {
            }
          }).show();
    }
  }

  /**
   * Get result from camera
   */
  @Override
  public void onActivityResult(int requestCode, int resultCode, Intent _intent) {
    if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_FROM_CAMERA) {
      ImageDisplayer displayer = new ImageDisplayer();
      displayer.execute(origFile);
    }
  }

  /**
   * This is it.
   * 
   */
  private class ImageDisplayer extends AsyncTask<File, Drawable, Drawable> {
    @Override
    protected void onPreExecute() {
      progressDialog = ProgressDialog.show(mContext, "Loading", "");
    }

    private void logTag(String[] _keyValue) {
      if (_keyValue != null && _keyValue[0] != null && _keyValue[1] != null){
      Log.v(LOGTAG, _keyValue[0] + ": " + _keyValue[1]);
      }
    }

    @Override
    protected Drawable doInBackground(File... _path) {
      ExifManager exifManager;
      ExifDriver exifDriver = ExifDriver
          .getInstance(_path[0].getAbsolutePath());
      if (exifDriver != null) {
        exifManager = new ExifManager(exifDriver, mContext);
        // We can modify some info
        exifManager.setArtist("The artist");
        exifManager.setUserComment("User comment ");
        // Save the image - do not use the same one !
        exifDriver.save(modifiedFile.getAbsolutePath());
        // Here we go - read info from the modified Exif file
        exifDriver = ExifDriver.getInstance(modifiedFile.getAbsolutePath());
        if (exifDriver != null) {
          exifManager = new ExifManager(exifDriver, mContext);
          logTag(exifManager.getExifRelated(ExifDriver.TAG_USER_COMMENT));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_IMAGE_WIDTH));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_IMAGE_HEIGHT));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_BITS_PER_SAMPLE));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_COMPRESSION));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_PHOTOMETRIC_INTERPRETATION));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_ORIENTATION));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_SAMPLES_PER_PIXEL));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_PLANAR_CONFIGURATION));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_YCBCR_SUBSAMPLING));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_YCBCRPOSITIONING));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_XRESOLUTION));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_YRESOLUTION));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_RESOLUTION_UNIT));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_STRIP_OFFSETS));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_ROWS_PER_STRIP));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_STRIP_BYTECOUNTS));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_TRANSFER_FUNCTION));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_WHITE_POINT));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_PRIMARY_CHROMATICITIES));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_YCBCR_COEFICIENTS));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_REFERENCE_BLACK_WHITE));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_DATETIME));
          logTag(exifManager
              .getMainImageRelated(ExifDriver.TAG_IMAGE_DESCRIPTION));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_MAKE));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_MODEL));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_SOFTWARE));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_ARTIST));
          logTag(exifManager.getMainImageRelated(ExifDriver.TAG_COPYRIGHT));
          // IFD Exif tags
          logTag(exifManager.getExifRelated(ExifDriver.TAG_EXIF_VERSION));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_FLASHPIX_VERSION));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_COLOR_SPACE));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_COMPONENT_CONFIGURATION));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_COMPRESSED_BITS_PER_PIXEL));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_PIXEL_X_DIMENSION));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_PIXEL_Y_DIMENSION));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_MARKER_NOTE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_USER_COMMENT));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_RELATED_SOUND_FILE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_DATETIME_ORIGINAL));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_DATETIME_DIGITIZED));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SUB_SEC_TIME));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_SUB_SEC_TIME_ORIGINAL));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_SUB_SEC_TIME_DIGITIZED));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_IMAGE_UNIQUE_ID));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_EXPOSURE_TIME));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_FNUMBER));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_EXPOSURE_PROGRAM));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_SPECTRAL_SENSITIVITY));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_ISO_SPEED_RATINGS));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_OECF));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SHUTTER_SPEED_VALUE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_APERTURE_VALUE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_BRIGHTNESS_VALUE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_EXPOSURE_BIAS_VALUE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_MAX_APERTURE_VALUE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SUBJECT_DISTANCE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_METERING_MODE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_LIGHT_SOURCE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_FLASH));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_FOCAL_LENGTH));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SUBJECT_AREA));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_FLASH_ENERGY));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_SPATIAL_FREQUENCY_RESPONSE));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_FOCAL_PLANE_X_RESOLUTION));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_FOCAL_PLANE_Y_RESOLUTION));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_FOCAL_PLANE_RESOLUTION_UNIT));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SUBJECT_LOCATION));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_EXPOSURE_INDEX));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SENSING_METHOD));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_FILE_SOURCE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SCENE_TYPE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_CFA_PATTERN));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_CUSTOM_RENDERED));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_EXPOSURE_MODE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_WHITE_BALANCE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_DIGITAL_ZOOM_RATIO));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_FOCAL_LENGTH_35MM_FILM));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SCENE_CAPTURE_TYPE));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_GAIN_CONTROL));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_CONTRAST));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SATURATION));
          logTag(exifManager.getExifRelated(ExifDriver.TAG_SHARPNESS));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_DEVICE_SETTING_DESCRIPTION));
          logTag(exifManager
              .getExifRelated(ExifDriver.TAG_SUBJECT_DISTANCE_RANGE));
          // Thumbnail tags
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_IMAGE_WIDTH));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_IMAGE_HEIGHT));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_JPEG_INTERCHANGE_FORMAT));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_BITS_PER_SAMPLE));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_COMPRESSION));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_PHOTOMETRIC_INTERPRETATION));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_ORIENTATION));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_SAMPLES_PER_PIXEL));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_PLANAR_CONFIGURATION));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_YCBCR_SUBSAMPLING));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_YCBCRPOSITIONING));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_XRESOLUTION));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_YRESOLUTION));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_RESOLUTION_UNIT));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_STRIP_OFFSETS));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_ROWS_PER_STRIP));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_STRIP_BYTECOUNTS));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_TRANSFER_FUNCTION));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_WHITE_POINT));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_PRIMARY_CHROMATICITIES));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_YCBCR_COEFICIENTS));
          logTag(exifManager
              .getThumbnailRelated(ExifDriver.TAG_REFERENCE_BLACK_WHITE));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_DATETIME));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_MAKE));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_MODEL));
          logTag(exifManager.getThumbnailRelated(ExifDriver.TAG_SOFTWARE));
          // IFD GPS tags
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_VERSION_ID));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_LATITUDE_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_LATITUDE));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_LONGITUDE_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_LONGITUDE));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_ALTITUDE_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_ALTITUDE));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_TIME_STAMP));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_SATELITES));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_STATUS));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_MEASURE_MODE));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DOP));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_SPEED_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_SPEED));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_TRACK_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_TRACK));
          logTag(exifManager
              .getGpsRelated(ExifDriver.TAG_GPS_SLMG_DIRECTION_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_SLMG_DIRECTION));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_MAP_DATUM));
          logTag(exifManager
              .getGpsRelated(ExifDriver.TAG_GPS_DEST_LATITUDE_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DEST_LATITUDE));
          logTag(exifManager
              .getGpsRelated(ExifDriver.TAG_GPS_DEST_LONGITUDE_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DEST_LONGITUDE));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DEST_BEARING_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DEST_BEARING));
          logTag(exifManager
              .getGpsRelated(ExifDriver.TAG_GPS_DEST_DISTANCE_REF));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DEST_DISTANCE));
          logTag(exifManager
              .getGpsRelated(ExifDriver.TAG_GPS_PROCESSING_METHOD));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_AREA_INFORMATION));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DATE_STAMP));
          logTag(exifManager.getGpsRelated(ExifDriver.TAG_GPS_DIFFERENTIAL));
        } else {
          Log.e(LOGTAG,
              "Could not read exif info from the modified image - and this is bad");
        }
      } else {
        Log.e(LOGTAG, "Could not read exif info from an original image");
      }
      return Drawable.createFromPath(_path[0].getAbsolutePath());
    }

    @Override
    protected void onPostExecute(Drawable _drawable) {
      imagePreview.setImageDrawable(_drawable);
      progressDialog.dismiss();
    }
  }
}

You can look at the code of the driver here:
https://code.google.com/p/exif-driver/source/browse/src/rcs34/android/libs/ExifDriver/
or you can cone the code from git repository
git clone https://code.google.com/p/exif-driver/

The driver tries to conform with Exif Version 2.2. For more info see http://exif.org/ . It can handle both - pure Exif images and JFIF images with included Exif data. Of course there is a relatively high probability of bugs, because the class is quite young and not well tested. You are welcome to send me images, which the ExifDriver will not work with.

 Hope this will work for you.