Friday, May 27, 2011

Praystation - An Android App

My first Android app is a wee little thing that draws an arrow pointing to Mecca from wherever you are. As such, it demonstrates some use of the Locator Api as well as some Canvas drawing and rotating

To begin with, the AndroidManifest.xml file contains simply a text area with a canvas beneath it. It also requires location permissions:

<manifest android:versioncode="1" android:versionname="1.0" 

package="com.praystation" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-sdk android:minsdkversion="8"> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"> <application android:icon="@drawable/arrow" android:label="@string/app_name"> <activity android:label="@string/app_name" android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"> <category android:name="android.intent.category.LAUNCHER"> & </category> </action> </intent-filter> </activity> </application></uses-permission></uses-permission></uses-sdk></manifest>

I have an 'arrow.png' file in res/drawable for the app logo as well as for the arrow used in this app itself.

The MainActivity finds the best LocationManager and gets your location (assuming you have gps enabled o your device, of course). It also takes in the fixed coordinates of Mecca and calculates the distance and bearing from where you are to where it is:

package com.praystation;

import android.app.Activity;
import java.util.List;

import android.graphics.Canvas;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity  implements LocationListener {
    private static final String TAG = "LocationDemo";
    private static final String[] S = { "Out of Service",
            "Temporarily Unavailable", "Available" };
    
    private TextView output;
    private LocationManager locationManager;
    private Location mMarkedLocation;
    private String bestProvider;
    
    public static float sBearing = 0.0f;
    public static float sDistance = 0.0f;
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // Get the output UI
        output = (TextView) findViewById(R.id.output);

        // Get the location manager
        locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);

        Criteria criteria = new Criteria();
        bestProvider = locationManager.getBestProvider(criteria, false);

        Location location = locationManager.getLastKnownLocation(bestProvider);
        printLocation(location);
        
        mMarkedLocation = new Location("Mecca");
        mMarkedLocation.setLatitude(21.4266667);
        mMarkedLocation.setLongitude(39.8261111);
        
        sDistance = location.distanceTo(mMarkedLocation) / 1000;
        output.append(String.format("\n\ndistance to Mecca: %.2f kilometers\n", sDistance));
        
        sBearing = location.bearingTo(mMarkedLocation);
        output.append(String.format("\n\nbearing to Mecca: %.2f degrees\n", sBearing));
    }

    /** Register for the updates when Activity is in foreground */
    @Override
    protected void onResume() {
        super.onResume();
        locationManager.requestLocationUpdates(bestProvider, 20000L, 1.0f, this);
    }

    /** Stop the updates when Activity is paused */
    @Override
    protected void onPause() {
        super.onPause();
        locationManager.removeUpdates(this);
    }

    public void onLocationChanged(Location location) {
        printLocation(location);
    }

    public void onProviderDisabled(String provider) {
        // let okProvider be bestProvider
        // re-register for updates
//        output.append("\n\nProvider Disabled: " + provider);
    }

    public void onProviderEnabled(String provider) {
        // is provider better than bestProvider?
        // is yes, bestProvider = provider
//        output.append("\n\nProvider Enabled: " + provider);
    }

    public void onStatusChanged(String provider, int status, Bundle extras) {
//        output.append("\n\nProvider Status Changed: " + provider + ", Status="
//                + S[status] + ", Extras=" + extras);
    }

    private void printProvider(String provider) {
        LocationProvider info = locationManager.getProvider(provider);
//        output.append(info.toString() + "\n\n");
    }

    private void printLocation(Location location) {
//        if (location == null)
//            output.append("\nLocation[unknown]\n\n");
//        else
//            output.append("\n\n" + location.toString());
    }
    
}

The Panel class does the actual drawing of the canvas, taking the arrow image and rotating it in the direction of the static calculated bearing:

package com.praystation;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class Panel extends SurfaceView implements SurfaceHolder.Callback{
    private CanvasThread canvasthread;
    
    public Panel(Context context, AttributeSet attrs) {
        super(context, attrs); 
        // TODO Auto-generated constructor stub
        getHolder().addCallback(this);
        canvasthread = new CanvasThread(getHolder(), this);
        setFocusable(true);
    }

     public Panel(Context context) {
           super(context);
            getHolder().addCallback(this);
            canvasthread = new CanvasThread(getHolder(), this);
            setFocusable(true);
        }

    @Override
    public void onDraw(Canvas canvas) {
        Log.d("ondraw", "lefutott");
        Paint paint = new Paint();
        int isRusty = 1;

        Bitmap arrow = BitmapFactory.decodeResource(getResources(),
                R.drawable.arrow2);
        canvas.drawColor(Color.BLACK);
        
        Matrix rotateMatrix = new Matrix();
        rotateMatrix.setRotate(MainActivity.sBearing, canvas.getWidth()/2, canvas.getHeight()/2);
        canvas.drawBitmap(arrow, rotateMatrix, null);
    }
    

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        // TODO Auto-generated method stub
        
    }
    
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        canvasthread.setRunning(true);
        canvasthread.start();    
    }
    
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        boolean retry = true;
        canvasthread.setRunning(false);
        while (retry) {
            try {
                canvasthread.join();
                retry = false;
            } catch (InterruptedException e) {
                // we will try it again and again...
            }
        }

    }
}

Finally, we have a CanvasThread class

package com.praystation;

import android.graphics.Canvas;
import android.view.SurfaceHolder;

public class CanvasThread extends Thread {
    private SurfaceHolder _surfaceHolder;
    private Panel _panel;
    private boolean _run = false;

    public CanvasThread(SurfaceHolder surfaceHolder, Panel panel) {
        _surfaceHolder = surfaceHolder;
        _panel = panel;
    }

    public void setRunning(boolean run) {
        _run = run;
    }

    @Override
    public void run() {
        Canvas c;
        while (_run) {
            c = null;
            try {
                c = _surfaceHolder.lockCanvas(null);
                synchronized (_surfaceHolder) {
                    _panel.onDraw(c);
                }
            } finally {
                // do this in a finally so that if an exception is thrown
                // during the above, we don't leave the Surface in an
                // inconsistent state
                if (c != null) {
                    _surfaceHolder.unlockCanvasAndPost(c);
                }
            }
        }
    }
}

No comments:

Post a Comment