Tuesday, October 6, 2015

GlyphView: Stale view: javax.swing.text.BadLocationException: Length must be positive


In this article I will show you, how you can get rid of the "GlyphView: Stale view: javax.swing.text.BadLocationException: Length must be positive" exception in JAVA Swing's JTextPane

If you are trying to develop your own cool text editor based on the JTextPane component, you probably have seen the message, which actually tells you, that you are trying to work with some incorrect offset/length combination.

The cause of the situation may be on your side and you should be sure for example, that you do modify the JTextPane's content only from the Dispatch thread (SwingUtilities.invokeLater is your friend).

If you are "pretty sure", that you do everything in the right way and your program keeps to fail with the BadLocationException, you can try the following workaround:

You will need your own implementations of the JTextPane, EditorKit and StyledDocument. You can put them all to one single class.
public class SCADTextPane extends JTextPane {

  private class Document extends DefaultStyledDocument {

    @Override
    public void getText(int _offset, int _length, Segment _txt) {
      if(_length<0){
        System.out.println("Getting rid of negative length "+_length);
       }
      try {
        super.getText(_offset, Math.max(_length, 0), _txt);
      } catch (Exception _e) {
        Logger.logError(new Exception("Offset/length/text: " + _offset + "/" + _length));
        Logger.logError(_e);
      }
    }
  }

  private class Kit extends StyledEditorKit {

    @Override
    public Document createDefaultDocument() {
      return new Document();
    }
  }

  @Override
  public EditorKit createDefaultEditorKit() {
    return new Kit();
  }
}


You can easily see, what makes the magic. You just grab the _length parameter of the getText() method and make sure that it's value is positive. You can of course do the similar with the _offset parameter.

Hope this will help you.



Thursday, January 22, 2015

Cooley–Tukey FFT algorithm


In this article I will show you, how you can compute the Cooley–Tukey FFT algorithm using the pure JAVA language.

If you are trying to work with some kind of signal analysis, you have probably learned that the FFT is the most widely used technique to extract the frequency spectrum from a signal.

You have of course many options to get some working FFT library. But some of them are licensed, and some of them are not easy to use nor well documented. If you want only to do some simple experiments with the FFT, you can freely use the code which I share On code.google.com. Once you add the FFTCooleyTukey class to your project, you can use it as follows:

    ...
    int SAMPLE_SIZE=1024;
    double[][] sample = new double[2][SAMPLE_SIZE];
    double[][]fftValues = new double[2][SAMPLE_SIZE];
    double[][]ifftValues = new double[2][SAMPLE_SIZE];
    //Read values and zero the imaginary part
    for(int i=0;i<SAMPLE_SIZE;i++){
      sample[0][i]=source[i];
      sample[1][i]=0;
    }
    System.out.println("\nOrig: "+Arrays.toString(sample[0]));
    try {
      fft.fft(sample, fftValues);
      System.out.println("FFT amplitudes: "+Arrays.toString(fftValues[0]));
      //Ask for aprticular frequency components
      System.out.println("200Hz component: "+fftValues[0][(int)Math.round(fft.getIndexOf(200))]);
      System.out.println("500Hz component: "+fftValues[0][(int)Math.round(fft.getIndexOf(500))]);
      fft.ifft(fftValues,ifftValues);
      System.out.println("IFFT amplitudes: "+Arrays.toString(ifftValues[0]));
    } catch (Exception ex) {
      Logger.getLogger(TunerPitchAnalyzer.class.getName()).log(Level.SEVERE, null, ex);
    }
    ...



Usage examples:
You can for example simply find the fftValues[0] for an index which holds the highest value and directly take the signal's base frequency like this:
    nFreq = fft.getFreq(maxIndex);

Or you can build a simple equalizer. Just multiply correspondent values in the fftValues[0] with a gain coefficient. The signal restored with the ifft() will then reflect the changes in the frequency spectrum.

Fill free to download the source on href="https://code.google.com/p/fft-processor/source/browse/trunk/FFTCooleyTukey.java

Hope this will help you.



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.

Sunday, September 2, 2012

Load local and remote image from gallery by Intent

In this article you will learn, how images from gallery can be load into your activity no matter if they are local or stored remotely (i.e. in Picasa).

If you are interested in how to load image from gallery to your activity, you almost certainly know, that you have to use Intent.ACTION_PICK and startActivityForResult. You probably know, that the result intent, which you will obtain by onActivityResult contains Uri of the selected image and you can get the Uri by the getData method. Maybe you have learned, that Uri got by the getData is not Uri of actual file, but only a kind of "database key".

Commonly used (wrong) approach uses ContentResolver query, which is supposed to return cursor, that holds the image file path in the MediaStore.Images.Media.DATA column.

This method will work well for local files. For files stored remotely, i.e. in Picasa gallery this method will work only on some (older) versions of Android. I have learned, that my Android 2.3 on my LG P350 downloads remote images which I select in gallery to Downloads folder, so they become local. But Android 3.2 on my PackardBell tablet does not do any download automatically and therefore above mentioned approach fails and I get Null pointer error when I try to work with remotely stored images.

Following code shows another approach, which seems to work with both local and remote images well. It uses ContentProviderClient and ParcelFileDescriptor. It loads Image to temp file and works with this local copy. Note, thet _path[0] holds Uri got by getData method from the result Intent.
@Override
    protected Drawable doInBackground(Uri... _path) {
      file = null;
      // This is the key line. Content provider client gives us access to
      // file no matter if it is a local or a remote one
      ContentProviderClient client = getContentResolver()
          .acquireContentProviderClient(_path[0]);
      try {
        // Here we save copy of the file to temporary
        ParcelFileDescriptor descriptor = client.openFile(_path[0], "r");
        AutoCloseInputStream is = new AutoCloseInputStream(descriptor);
        file = File.createTempFile("image", ".jpg", getDir(null, MODE_PRIVATE));
        OutputStream outS = new FileOutputStream(file);
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = is.read(buf)) > 0) {
          outS.write(buf, 0, len);
        }
        is.close();
        outS.close();
Complete code of test activity follows. To make it work, you have to create activity_galery_get.xml which will provide ImageView and Button.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/previewFrame"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/imagePreview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:contentDescription="Image to share" >
        </ImageView>
        
        <Button
            android:id="@+id/bFromGallery"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="left"
            android:onClick="handleFromGallery"
            android:text="Select image from gallery" />
        
    </FrameLayout>

Here is the complete activity code:
public class GaleryGet extends Activity {
  private final String LOGTAG = getClass().getName();
  private final int REQUEST_IMAGE = 33;
  private ProgressDialog progressDialog;
  private File file = null;
  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_galery_get);
    imagePreview = (ImageView) findViewById(R.id.imagePreview);
  }

  /**
   * Button handler. Also very common one. Just create Intent and run it.
   */
  public void handleFromGallery(View _view) {
    Intent i = new Intent(Intent.ACTION_PICK,
        android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(i, REQUEST_IMAGE);
  }

  /**
   * Get result from gallery. Well we extract URI from intent and run
   * ImageDisplayer
   */
  @Override
  public void onActivityResult(int requestCode, int resultCode, Intent _intent) {
    if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_IMAGE) {
      ImageDisplayer displayer = new ImageDisplayer();
      if(_intent.getData()!=null){
      displayer.execute(_intent.getData());
      }
    }
  }

  /**
   * This is it. here we can see, how to process Uri from gallery result
   * in a proper way to get image displayed.
   * 
   */
  private class ImageDisplayer extends AsyncTask<Uri, Drawable,Drawable> {
    @Override
    protected void onPreExecute(){
      progressDialog = ProgressDialog.show(mContext, "Loading", "");
    }
    @Override
    protected Drawable doInBackground(Uri... _path) {
      file = null;
      // This is the key line. Content provider client gives us access to
      // file no matter if it is a local or a remote one
      ContentProviderClient client = getContentResolver()
          .acquireContentProviderClient(_path[0]);
      try {
        // Here we save copy of the file to temporary
        ParcelFileDescriptor descriptor = client.openFile(_path[0], "r");
        AutoCloseInputStream is = new AutoCloseInputStream(descriptor);
        file = File.createTempFile("image", ".jpg", getDir(null, MODE_PRIVATE));
        OutputStream outS = new FileOutputStream(file);
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = is.read(buf)) > 0) {
          outS.write(buf, 0, len);
        }
        is.close();
        outS.close();
      } catch (RemoteException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
      } catch (FileNotFoundException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
      } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
      // And finally, we display the image
      String filePath = file.getAbsolutePath();
      return Drawable.createFromPath(filePath);
    }

    @Override
    protected void onPostExecute(Drawable _drawable) {
      imagePreview.setImageDrawable(_drawable);
      progressDialog.dismiss();
    }
  }
}
Hope this will work for you.

Wednesday, August 29, 2012

Detect onDoubleTap or onLongPress on a MapView

In this article you will learn how a long click or a double click can be detected on a MapView object.

If your application uses a MapView from Google APIs, you could be interested in how to detect some simple gestures like a double tap (double click) or a long tap (long click).

As you probably know already, you have to use ItemizedOverlay subclass to work with tap events on a MapView.  Sadly ItemizedOverlay provides only onTouchEvent(MotionEvent) and onTap(int), which can be a little bit challenging to use for detecting more complex events.

Fortunately, the solution is quite simple as we can use GestureDetector and SimpleOnGestureListener. Following code sample shows, how to do it quite simply:

  // Overlay
  private class Overlays extends ItemizedOverlay {
    private ArrayList<overlayitem> overlays = new ArrayList<overlayitem>();

    private GestureDetector gestureDetector;

    //Private MapGestureListener which will listen to just
    //a long and a double press
    private class MapGestureListener extends SimpleOnGestureListener {
      @override
      public void onLongPress(MotionEvent _motion) {
        //Do something nice here
      }

      @override
      public boolean onDoubleTap(MotionEvent _motion) {
      //Do something nice here
       return true;
      }
    }
    //ItemizedOverlay constructor. Register GestureDetector and 
    //MapGestureListener right here
    public Overlays(Drawable defaultMarker) {
      super(boundCenterBottom(defaultMarker));
      gestureDetector = new GestureDetector(GApp.context,
          new MapGestureListener());
    }
    //onTap() method should work normally
    @Override
    public boolean onTap(int _index) {
      Toast.makeText(GApp.context, overlays.get(_index).getTitle(),
          Toast.LENGTH_SHORT).show();
      return true;
    }
    //Ship onTouchEvent to our GestureDetector and return what it returns
    @Override
    public boolean onTouchEvent(MotionEvent _motion, MapView _mapView) {
      return gestureDetector.onTouchEvent(_motion);
    }
  }

Hope this will work for you.