October 12th, 2009

More lessons to learn from the iPhone App store

Marco Arment has a great post on his blog  about the two App Stores…no not the Apple and Android app store.  But the two sides of the Apple App store and expectations that developers of mobile games and applications should have when positioning their apps.

He makes some great points about “one-hit wonder apps” vs sustained apps, lessons I believe can be utilized by developers in other “app” stores as well.

October 11th, 2009

Email via Android Intent code snippet

I’m moving away from using the Android email client in Note To Me, but wanted to share the snippet to launch the client.  And who knows, maybe I’ll add it back in if people want it as an option.

        Button sendBtn = (Button) findViewById(R.id.send_btn_id);
        sendBtn.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v)
            {
            	Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
            	emailIntent.setType("text/html");
            	emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL,
                  new String[]{ sendToAddress } );

            	emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject );
            	emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, msg );

            	hideKeyboard();

            	NoteToMe.this.startActivity(emailIntent);
            	NoteToMe.this.finish();
            }
        });

October 8th, 2009

I can handle these numbers

I know this is just fanciful dreaming, and this sort of thinking is one reason for the internet bubble in the late 90’s and burst in early 2000…but it’s fun anyway!

Given the following numbers

  • According to Engadget the Android market share could hit 14% by 2012
  • Current number of smart phones world wide is 235.6 million according to this article from Yahoo
  • Android’s market share is currently less then 2% (according to the Engadget article)

I think I can safely make the following pessimistic calculation:

read the rest of this entry… »

October 6th, 2009

Great advice for marketing mobile apps

Although Dan Grigsby speaks about iPhone development and the iPhone app store there is a ton of advice which easily carries over to the Android platform as well.  I plan on implementing many of his suggestions into both MediaDroid and Note To Me and the ways I go about promoting them.

October 4th, 2009

MediaDroid “mostly” working with Android 1.6

* UPDATE - Full and Trial version of MediaDroid now have fixes for Android 1.6 layout issues I was having.  Check them out in the Android Market and let me know what you think!

Just got updated to 1.6 from T-Mobile. I love the new searching capabilities and the Power Control Home screen Widget.

homescreen_widgets.png

But it looks like I have some work to be done for one of the layouts for MediaDroid.  Not sure what’s wrong yet, but it looks like the layout for the Manage Album screen isn’t configured correctly and the buttons have been force out of the screen.  Hopefully the fix isn’t to hard and I’ll get a new version of MediaDroid out soon!

mange_album_16oops.png

October 2nd, 2009

Buy more Android apps…please

This is my call to arms for all Android users…buy more apps and talk about them, show them to your friends family and co-workers, blog about them, tweet about them, post pictures, let’s talk this shit up!  Everyone is comparing Android to the iPhone or Palm…you’re wasting your time, let’s spend those valuable keystrokes talking about how awesome the apps are on Android market and not trying to make comparisons to other platforms.  The more marketing buzz we can create about Android apps and how hard the single developers or small dev shops are working to create these products the better it is for all of us.  Not only do I want my apps to be successful but I’d love to hear about Phil Symonds making a million bucks on Abduction or how the college students who developed Heat paid their way through college developing Android apps.

So, show some love, and make sure to spend the buck or two for the app which that guy or gal spent many’a hours creating and testing…and let others know also.  I hope all this doesn’t sound to much like self promotion, but I really believe that it behooves all of us to have a vibrant and profitable Android app market.

October 1st, 2009

MediaDroid now cycles images from the selected album

Using a Service and ACTION_SCREEN_OFF  I’ve successfully added a new feature to MediaDroid which cycles the images in your photo album.  Every time the screen is put to sleep, the next photo in the album is placed in the photo widget on the Home screen of your Android device.  Here’s a code snippet…

public class ChangeImageService extends Service
{
	private static final boolean LOGD = Constants.LOGD;

	private BroadcastReceiver _receiver;
	private static final String LOG_TAG = "ChangeImageService";

        @Override
	public void onCreate()
        {
		super.onCreate();

	    _receiver = new BroadcastReceiver()
	    {
	        public void onReceive(Context context, Intent intent)
	        {
	        	loadNextImage();
	        }
	    };

	    IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
	    registerReceiver(_receiver, filter);
	}

....
}

A couple of gotchas you have to watch out for… The ACTION_SCREEN_OFF Intent does not get picked up from the AndroidManifest.xml file so you have to assign it in code as shown above.  Also, I put the assignment in the onCreate method because the Service can get killed off.  If the Service is killed off, Android will schedule it to restart, but in order to catch the Screen Off broadcasts the Service must sign up again in the onCreate method…at least, this is what worked for me and all seems to be going well.  As always, the docs are a little sparse and I had to go through some trial and error to figure it out, but I’m pretty happy with the results.

The new feature (along with a bug fix) is available in both the Trial and Full version of MediaDroid in the Android market…check em out and let me know what you think!

October 1st, 2009

Email directly from Note To Me

I’m pretty excited about this…don’t remember how I found Nilvec, but thanks to a pair of blogs on their site I’ve been able to integrate dispatching of emails directly into Note To Me (this feature isn’t yet available for the version in the Android market) so the user doesn’t have to go through the Gmail client, very slick.  Check out these posts…

Sending Email Without User Interaction In Android

Email Sending Example

They packaged the Apache classes very nicely and made it easy to get the email set up and running.  Now for all the other features that are needed because I’m implementing my own email client…ie, making sure the emails are sent later if there is no wireless connection.

September 22nd, 2009

Full version of MediaDroid on the Android Market

Just wanted to post a quick note that I’ve released a full version of MediaDroid to the Android Market.  I’ve got lots more feature coming up and there’s a couple of bugs to iron out, but I think it’s feature complete enough for a first version.

Would love to get your feedback, good or bad!  More information with screen shots at http://www.mgmblog.com/mediadroid.

September 21st, 2009

My implementation of ImageCache

Been awhile since I’ve posted any code and thought it was about time.  The image gallery has definitely been one of the most challenging parts of the MediaDroid application so far.  I even seem to have uncovered a bug in one of the earlier versions.  But thanks to some advice from the Google I/O talk by Romain Guy I settled on an implementation using SoftReferences and the AsyncTask class.  If you have more experience with threading then I do, I’d be interested in your opinion!

The task of the ImageCache is to load Bitmaps as they are requested.  From the outside the Bitmaps are requested using the get method which takes the Uri of the Bitmap to load, and the ImageView which will eventually be updated with the Bitmap. A single thread waits for the requests or jobs as I call them to be added to a job list.

The ImageCache follows the Singleton pattern and is initiated using the follow call,

ImageCache.getInstance().executeLoadImagesTask( context );

This call initiates a thread which waits for jobs to be posted on the job list.

A Bitmap is retrieved from the ImageCache with the following call

Bitmap bm = ImageCache.getInstance().get( _thumbUri.toString(), _imgVw );

If the image isn’t initially in the cache, a default Bitmap is returned, then a job is added to the job list.  This job contains the Uri to the Bitmap and and the ImageView to update when the Bitmap is done loading.

I also extended ImageView to contain a Uri for the image it displays. When the image is loaded into the UriImageView it is checked against the one that is currently assigned. Otherwise, the ImageView will cycle through all the images that are loaded, kind of a cool effect, but not the best result for the UI.

As always, sorry for the funky formatting, someday I’ll find and install a proper CSS for computer code.  Be weary of cut-n-paste, I’m not sure if I caught all the formatting issues.


public class ImageCache
{
	private static final String LOG_TAG = "ImageCache";
	private static final boolean LOGD = true;

	private static ImageCache _instance = null;
	private final HashMap<String, SoftReference<Bitmap>> _cache;

	private Context _ctx;
	private static Bitmap defaultBitmap;
	private Object _lock;
	private ArrayList<ImageJob> jobs =  new ArrayList<ImageJob>();

	public static ImageCache getInstance()
	{
		if ( _instance == null )
		{
			_instance = new ImageCache();
		}

		return _instance;
	}

	private ImageCache()
	{
		_lock = new Object();
		_cache = new HashMap&ltString, SoftReference<Bitmap>>();
	}

	public void executeLoadImagesTask( Context ctx )
	{
		_ctx = ctx;
        	defaultBitmap = BitmapFactory.decodeResource( _ctx.getResources(), R.drawable.camera_icon );
		new LoadImagesTask().execute(null);
	}

	public void put( String key, Bitmap bitmap )
	{
		_cache.put( key, new SoftReference<Bitmap>(bitmap) );
	}

	public void clear()
	{
		_cache.clear();
	}

	public Bitmap get( String imguri, UriImageView viewToUpdate )
	{

		Bitmap bm = null;
		SoftReference>Bitmap< reference = _cache.get(imguri);

		if ( reference != null )
		{
			bm = reference.get();
		}

		if ( bm == null )
		{
			bm = defaultBitmap;
			postJob( new ImageJob( imguri, viewToUpdate ) );
		}

		return bm;
	}

	/**
	 * Called after the bitmap has been retrieved bitmap from the
	 * sdcard
	 *
	 * @param jobs
	 */
	public void addImage( ImageJob... jobs )
	{
		ImageJob job = jobs[0];
		final UriImageView view = job.getViewToUpdate();
		final Bitmap bm = job.getBitmap();
		final Uri imguri = job.getImageUri();

		put( imguri.toString(), bm );

		view.post( new Runnable() {

			public void run()
			{
				view.setImage(imguri, bm);
			}

		});
	}

	private void dbg( String msg )
	{
		if (LOGD) Log.d( LOG_TAG, msg );
	}

	private void postJob( final ImageJob job )
	{
    		dbg( "Thread: " + Thread.currentThread().getName() + " posting job " + job.getImageUri() );
    		synchronized(_lock)
    		{
    			jobs.add(job);
    			_lock.notify();
    		}
	}

	private class ImageJob
	{
		private Uri _imageUri;
		private UriImageView _viewToUpdate;
		private Bitmap _bm;

		public ImageJob( Uri imageUri, UriImageView viewToUpdate )
		{
			_imageUri = imageUri;
			_viewToUpdate = viewToUpdate;
		}

		public ImageJob( String imageUri, UriImageView viewToUpdate )
		{
			this( Uri.parse( imageUri ), viewToUpdate );
		}

		public Uri getImageUri()
		{
			return _imageUri;
		}

		public UriImageView getViewToUpdate()
		{
			return _viewToUpdate;
		}

		public void setBitmap( Bitmap bm )
		{
			_bm = bm;
		}

		public Bitmap getBitmap()
		{
			return _bm;
		}
	}

	private class LoadImagesTask extends AsyncTask<Object, ImageJob, Integer>
	{
		@Override
		protected Integer doInBackground(Object... params)
		{
			Thread.currentThread().setName( "LoadImagesTask" );

			while( true )
			{
				int jobsize = 0;

				synchronized( _lock )
				{
					jobsize = jobs.size();
				}

				if ( jobsize > 0 )
				{
					synchronized( _lock )
					{
						ImageJob job = jobs.remove(0);
						dbg( "performing image job " + job.getImageUri() );

						try
						{
							job.setBitmap( ImgUtils.buildBitmap(_ctx, job.getImageUri(), 4) );
							onProgressUpdate( job );
						}
						catch (IOException e)
						{
							e.printStackTrace();
						}
					}
				}
				else
				{
					try
					{
						synchronized( _lock )
						{
							dbg( "Thread: " + Thread.currentThread().getName() + " waiting for jobs" );
							_lock.wait();
							dbg( "Thread: " + Thread.currentThread().getName() + " got a job" );
						}
					}
					catch (InterruptedException e)
					{
						e.printStackTrace();
					}
				}
			}
		}

	    @Override
	    public void onProgressUpdate(ImageJob... job)
	    {
	        addImage( job );
	    }

	    @Override
	    public void onPostExecute(Integer imagesLoaded)
	    {
	    	dbg( "loaded " + imagesLoaded + " images" );
	    }

	}
}

Again, I’d love to hear any comments you may have about the implementation, one thing about bootstrapping and doing all the coding yourself is it’s much harder to find code reviewers!