Improving the custom Android Gallery

I was looking for a custom gallery for displaying and selecting images on an Android phone, and while I found the sample code here to be a good start it needed some work before it was useful.

Before starting, it is worth pointing out that the Android API supplies a perfectly useful image gallery, and the StackOverflow thread how to pick a image from gallery (SD Card) for my app in android? has everything you need to get plug it into your own application.

If you require customised behaviour it isn’t too hard to get something rough working, so go to the original thread and create the ImageThumbnailsActivity and ViewImage classes, then configure the AndroidManifest.xml, res/layout/main.xml and missing res/values/strings.xml files.

The layout specifies a single GridView to hold the images, and the main ImageThumbnailsActivity activity performs two main jobs. First it loads all existing thumbnails and provides the code necessary to interact with the thumbnail if it is selected, and it has a nested adapter to manage the display of the thumbnails using a Cursor position into the GridView.

There are quite a few comments in the original posting indicating that the code is not quite right, and if you run it on a phone with a decent history of photos you’ll get some unusual behaviour. The two main issues are that firstly there are far more thumbnails displayed than there are images stored, and secondly that the images displayed are repeated as you scroll through the gallery.

The first issue can be resolved using the StackOverflow thread here and involves initially loading the full images rather than the thumbnail, but associating with the thumbnail when displaying on the grid. This means that we don’t try to show a thumbnail for an image that no longer exists or is incorrect for some other reason.

The second issue is a little more involved, but related to the fact that the nested ImageAdapter class will attempt to reuse View instances if possible (in the getView method), and the code provided assumes that if a View is returned to be reused that it is correct, which it is not. The GridView only creates as many View instances as required to fill the screen. Once you scroll to a new section it asks if you want to reuse the instances that have scrolled off the screen. Obviously this is an acceptable reuse of resources, but the reused instances will be showing the thumbnail for a different picture and will need to be updated.

As a matter of style, I also made the following changes:

  • Remove all of the System.gc() calls, they aren’t required.
  • close cursors
  • make variables local where applicable
  • as stated we load images from the MediaStore.Images.Media rather than the MediaStore.Images.Thumbnails but return the thumbnail for display.
  • specify the MediaStore.Images.Thumbnails.MICRO_KIND for display. The benefit here is that the images are the size we intend to show and eliminited scaling. Feel free to play with settings but I found this the most pleasing layout and matches the existing Android gallery

Finally, if you look up the thumbnail when the View is visible on the screen, the scrolling is slow and limited to a row at a time rather than being able to race past images they do not want. Since we specify the thumbnails as the smallest size, I chose to load and cache the android.graphics.Bitmap for the thumbnails so they are ready to display when required.

Just to Review

We’re doing this to provide a customised image gallery. The Android gallery is fully functional and should suit most needs, but if for example you also needed to display the file name or size, date or other information, this solution allows a custom View on the GridView.

Everything else remains the same, and the updated ImageThumbnailsActivity looks like this:

public class ImageThumbnailsActivity extends Activity
{
    private int count;
    /**
     * Caching this is a trade off in memory versus performance.
     * We deliberated use the smallest thumbnails so the size of each should be small (92x92x1 byte = about 10kb each)
     * but can be significant if there are thousands of images on the device.
     */
    private Bitmap[] windows;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.image_thumbnail);
        init_phone_image_grid();
    }

    private void init_phone_image_grid()
    {
        final String[] columns = { MediaStore.Images.Media._ID };
        final String orderBy = MediaStore.Images.Media._ID;
        Cursor imagecursor = managedQuery(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, null, null, orderBy);
        int image_column_index = imagecursor.getColumnIndex(MediaStore.Images.Media._ID);
        this.count = imagecursor.getCount();
        this.windows = new Bitmap[this.count];
        for(int i=0;i<this.count;i++)
        {
            imagecursor.moveToPosition(i);
            int id = imagecursor.getInt(image_column_index);
            windows[i] = MediaStore.Images.Thumbnails.getThumbnail(getApplicationContext().getContentResolver(),
                    id, MediaStore.Images.Thumbnails.MICRO_KIND, null);
        }
        GridView imagegrid = (GridView) findViewById(R.id.PhoneImageGrid);
        imagegrid.setAdapter(new ImageAdapter(getApplicationContext()));
        imagegrid.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(@SuppressWarnings("rawtypes") AdapterView parent, View v, int position, long id)
            {
                String[] columns = { MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID };
                Cursor actualimagecursor = managedQuery(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, null, null, null);
                final int dataColumnIndex = actualimagecursor.getColumnIndex(MediaStore.Images.Media.DATA);
                final int idColumnIndex = actualimagecursor.getColumnIndex(MediaStore.Images.Media._ID);
                actualimagecursor.moveToPosition(position);
                final String filename = actualimagecursor.getString(dataColumnIndex);
                final long imageId = actualimagecursor.getLong(idColumnIndex);
                final Intent intent = new Intent(ImageThumbnailsActivity.this, ViewImage.class);
                intent.putExtra("filename", filename);
                intent.putExtra("dataUid", imageId);
                actualimagecursor.close();
                startActivity(intent);
            }
        });
        imagecursor.close();
    }

    public class ImageAdapter extends BaseAdapter
    {
        private Context mContext;

        public ImageAdapter(Context c)
        {
            mContext = c;
        }

        @Override
        public int getCount()
        {
            return count;
        }

        @Override
        public Object getItem(int position)
        {
            return position;
        }

        @Override
        public long getItemId(int position)
        {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent)
        {
            ImageView i = (ImageView)convertView;
            if(i!=null)
            {
               i.setImageBitmap(windows[position]);
            }
            else
            {
                i = new ImageView(mContext.getApplicationContext());
                i.setImageBitmap(windows[position]);
                i.setLayoutParams(new GridView.LayoutParams(92, 92));
            }
            return i;
        }
    }
}

running chromium os on the mac on virtualbox

Now that the Chromebook is out and I’ve speculated about the target audience, I wanted to give running the Google OS a shot.  The closest I know that you can get is running Chronium OS which is the open source version.

The VM

This is the first time I needed a virtual machine on my mac.  I decided to start with VirtualBox since it is free for personal use.  It met my needs, so I’m done.  I should try Fusion at some point, but I didn’t need it for this.  I started by downloading the 82MB download for VirtualBox.

Setting up the VM

Since the “versioned” copy only provides a VM Ware and USB stick image, I tried following the instructions to convert the USB image to a vgi virtualbox file.  (The USB download is 324 MB.)  Launching the VM that way just gave me a black screen.

Next I tried getting the nightly snapshot build for VirtualBox from the “vanilla” site.  That worked well and I got the Chromium login screen.

I created the VM both times. using 512 MB RAM and Linux Ubuntu 32 bit.

Taking a screenshot

The only thing that that wasn’t obvious in VirtualBox was how to take a screenshot.   Thanks to this Techmix post, I learned you need to press left command to return the keyboard to the host mac and then use the right command key (with shift + 4) to grab a screenshot and have it sent to the desktop of the host mac.  And you have to do this every time because the keyboard focus returns to the VM every time you command+tab back to it.

getting started with google os (chromium) – a step by step guide

I installed Google OS (Chromium really) in a virtual machine to try it out.  It really is as simple as advertised.  Here’s everything you have to do.

The first time you use the computer

  1. Tell the machine how to connect to the internet.  It was ethernet rather than wifi.  Presumably because the VM is networked to the host machine.
  2. Username/password for google account if have one.
  3. Image you’d like to use for identification.
  4. Practice with your touchpad.  You can see this online.  It’s pretty cool in that it picks up on the fact that I have a smart touchpad.    In my Safari browser on the host machine, I can drag and drop and the like.  In my Chrome browser on the guest, I cannot.  Both the laptop and external trackpads work fine in the Chrome browser on guest; it’s just the test page giving me problems.
  5. Usability issue: It took me about five minutes to realize that was it.  It’s a normal browser window from which yo can go to other websites.

The menu

Click time and “open date and time options” to get the control panel lite.  This is where you can say you don’t want passwords saved, set up security and other preferences.  You can also get here by clicking the wrench on the right side of the screen.

If you close the browser, you get the bookmarks bar.  It starts with an option to take you to the webstore which offers both free and paid apps.  You mean you didn’t want to play Angry Birds?  The other built in option is the file manager which appears to host local and cloud storage.  I was puzzled because I thought there was no local storage.

There options for the browser are easy to read.

The second login

It shows your cute icon/avatar.  It took about 15 seconds to launch.  I’m not sure how much of that is Chromium and how much is me launching a computer within my computer.  (For the more technical folks reading this, I imagine starting a real computer is going to be about the same or a little slower.)