Always On, Always Connected Week 5 - Mobile Media

Drawing

If you would like to programmatically create graphics or UI elements, you can extend the View class.

Example:
View:
package com.mobvcasting.simpledrawexample;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

public class DrawingView extends View {
    
	public DrawingView(Context context) {
        super(context);
    }
	
	/*  This and the next constructor are required for creating the view via XML */
	public DrawingView(Context context, AttributeSet attrs) {
		super(context,attrs);
	}
	
	public DrawingView(Context context, AttributeSet attrs, int defStyle) {
		super(context,attrs,defStyle);
	}
    
    /* Override this method to implement your drawing routines */
	@Override
    protected void onDraw(Canvas canvas) {
		Paint p = new Paint();
		
		p.setColor(Color.BLUE);
		//p.setARGB(a, r, g, b)

		int width = getWidth();
		int height = getHeight();

		canvas.drawLine(0, 0, width, height, p);
  		
		canvas.drawText("HELLO", 5, 10, p);
	}		
}		
		

Full Project in Git Repo: https://github.com/vanevery/SimpleDrawingExample

Touch

Of course, we could make a View that allowed you to effectively draw on it using Touch events.

Example:
View:
package com.mobvcasting.fingerdrawingexample;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

// We are extending ImageView so that we can have a persistent Bitmap and the means to display it
public class DrawingView extends ImageView implements OnTouchListener {
    
	public final static String LOGTAG = "DrawingView";
	
	Bitmap bitmap;
	Paint paint;
	Canvas canvas;

	float downX = 0;
	float downY = 0;
	float upX = 0;
	float upY = 0;
	
	public DrawingView(Context context) {
        super(context);
    }
	
	public DrawingView(Context context, AttributeSet attrs) {
		super(context,attrs);
	}
	
	public DrawingView(Context context, AttributeSet attrs, int defStyle) {
		super(context,attrs,defStyle);
	}
	
	public void init() {
		
        bitmap = Bitmap.createBitmap(this.getWidth(), this.getHeight(), Bitmap.Config.ARGB_8888);
        canvas = new Canvas(bitmap);
        paint = new Paint();
        paint.setColor(Color.RED);
        this.setImageBitmap(bitmap);
		this.setOnTouchListener(this);
	}

	@Override 
	protected void onSizeChanged (int w, int h, int oldw, int oldh)
	{
		Log.v(LOGTAG,"onSizeChanged:" + w + " " + h + " " + oldw + " " + oldh);
		if (w != 0 && h != 0) {
			init();
		}
	}
	
	@Override
	public boolean onTouch(View v, MotionEvent motionEvent) {
		
		if (bitmap != null) 
		{
			Log.v(LOGTAG,"onTouch, bitmap isn't null");
			
			int action = motionEvent.getAction();			
			switch (action) {
				case MotionEvent.ACTION_DOWN:
					downX = motionEvent.getX();
					downY = motionEvent.getY();
					break;
				case MotionEvent.ACTION_MOVE:
					break;
				case MotionEvent.ACTION_UP:
					upX = motionEvent.getX();
					upY = motionEvent.getY();
					canvas.drawLine(downX, downY, upX, upY, paint);
					invalidate();
					break;
				case MotionEvent.ACTION_CANCEL:
					break;	
				default: 
					break;
			}			
		} 
		return true;
	}	
}        
		

Full Example Git Repo: https://github.com/vanevery/FingerDrawingExample.git

Accelerometer

Example:
package com.mobvcasting.accelerometerexample;

import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.Log;

public class AccelerometerExampleActivity extends Activity implements SensorEventListener {

	private SensorManager sensorManager;
	private Sensor accelerometerSensor;

	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
		accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    }
	
	@Override
	protected void onResume() {
		super.onResume();
		sensorManager.registerListener(this, accelerometerSensor, SensorManager.SENSOR_DELAY_NORMAL);
	}	

	@Override 
	protected void onPause() {
		super.onPause();
    	sensorManager.unregisterListener(this);
    }
	
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
	}

	public void onSensorChanged(SensorEvent event) {
		Log.v("Accel","X: " + event.values[0]);
		Log.v("Accel","Y: " + event.values[1]);
		Log.v("Accel","Z: " + event.values[2]);
	}
}
		
Full Example Git Repo: https://github.com/vanevery/AccelerometerExample

Compass

Example:
/*
 * Compass Example with help from:
 * http://www.codingforandroid.com/2011/01/using-orientation-sensors-simple.html
 */

package com.mobvcasting.compass;

import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.view.View;

public class CompasExample extends Activity implements SensorEventListener {

	static final String LOGTAG = "Compass";
	
	SensorManager sensorManager;
    SensorEventListener sensorListener;
    
    Sensor magnometerSensor;
    Sensor accelerometerSensor;
    
    AniView animationView;
    
    float[] accelerometerMatrix;
    float[] magnometerMatrix;
    
    float azimuth = 0.0f;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 
        
        AniView animationView = new AniView(this);
        setContentView(animationView);

        sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
        accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        magnometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
    }
    
    public void onResume() {
        super.onResume();
        
        sensorManager.registerListener(this, accelerometerSensor, SensorManager.SENSOR_DELAY_NORMAL);
        sensorManager.registerListener(this, magnometerSensor, SensorManager.SENSOR_DELAY_NORMAL);
    }
    
    public void onPause() {
    	super.onPause();
    	sensorManager.unregisterListener(this);
    }
    
    public void onSensorChanged(SensorEvent event) {
    	
    	if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) 
    	{
    		magnometerMatrix = event.values;
    	}
    	else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) 
    	{
    		accelerometerMatrix = event.values;
    	}
    	
    	
    	if (magnometerMatrix != null && accelerometerMatrix != null) {
    		float R[] = new float[9];
    		float I[] = new float[9];
    		
    		//https://developer.android.com/reference/android/hardware/SensorManager.html
    		boolean rotationMatrixSuccess = SensorManager.getRotationMatrix(R, I, accelerometerMatrix, magnometerMatrix);
    	    
    		if (rotationMatrixSuccess) {
    	        float orientation[] = new float[3];
    	        SensorManager.getOrientation(R, orientation);
    	        azimuth = orientation[0]; // orientation contains: azimuth (around z axis), pitch (around y axis) and roll
    	    }    		
    	}
    	
    }

    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }

    class AniView extends View {

        public AniView(Context context) {
            super(context);
        }
        
    	@Override
        protected void onDraw(Canvas canvas) {
    		Paint p = new Paint();
    		p.setColor(Color.BLUE);

    		int width = getWidth();
    		int height = getHeight();
    		int centerx = width/2;
    		int centery = height/2;
    		canvas.drawLine(centerx, 0, centerx, height, p);
    		canvas.drawLine(0, centery, width, centery, p);
  
    		// Rotate the canvas with the azimuth      
    		canvas.rotate(-azimuth*360/(float)(2*Math.PI), centerx, centery);

    		p.setColor(Color.RED);
    		canvas.drawLine(centerx, -1000, centerx, +1000, p);
    		canvas.drawLine(-1000, centery, 1000, centery, p);
    		
    		canvas.drawText("N", centerx+5, centery-10, p);
    		canvas.drawText("S", centerx-10, centery+15, p);
  		
            invalidate();
    	}
    }
}        
		
Full Example Git Repo: https://github.com/vanevery/CompassExample

Location

Git Repo: https://github.com/vanevery/LocationTrackingExample Example: Requires Permissions:
android.permission.ACCESS_COARSE_LOCATION
android.permission.ACCESS_FINE_LOCATION
package com.mobvcasting.locationtracking;

import android.app.Activity; 
import android.content.Context; 
import android.location.Location; 
import android.location.LocationListener; 
import android.location.LocationManager; 
import android.location.LocationProvider; 
import android.os.Bundle; 
import android.util.Log; 
import android.widget.TextView;

/*
To receive location updates from the LocationManager, we'll have our activity implement LocationListener.
*/

public class LocationTracking extends Activity implements LocationListener {

	LocationManager lm; 
	TextView tv;

	public void onCreate(Bundle savedInstanceState) { 
		super.onCreate(savedInstanceState); 
		setContentView(R.layout.main);
		tv = (TextView) this.findViewById(R.id.location); 
		
		/*
		We get an instance of LocationManager by using the getSystemService method available
		in Context, which Activity is a subclass of therefore it is available to us.
		*/
		lm = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

		/*
		LocationManager offers us the ability to specify that we want our LocationListener, in this case, our activity, to be notified of location-related changes. We register our activity as the LocationListener by passing it in as the last argument to the requestLocationUpdates method.
		The first argument in the method is the location provider that we would like to use. The two location providers available are specified as constants in the LocationManager class. The one we are using here, NETWORK_PROVIDER, utilizes network services such as cell tower location or WiFi access point location to determine location. The other one available is GPS_PROVIDER, which provides location information utilizing GPS (Global Positioning Satellites). NETWORK_PROVIDER is generally a much faster but potentially less accurate location lookup than GPS. GPS may take a significant amount of time to acquire signals from satellites and may not work at all indoors or in areas where the sky is not clearly visible (midtown Manhattan, for instance).

		The second argument is the minimum amount of time the system will wait between "location changed" notifications. It is specified as a long representing milliseconds. Here we are using 60,000 milliseconds or 1 minute.
		The third argument is the amount of distance that the location needs to have changed before a "location changed" notification is given. This is specified as a float representing meters. Here we are using 5 meters.
		 */

		lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 6000l, 5.0f, this);
		lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 6000l, 5.0f, this);
	}

	/*
	When using the LocationManager, particularly when using GPS as the provider, it may be prudent to stop the location updates when the application is no longer in the foreground. This will conserve battery power. To do so, we can override the normal onPause or onStop method in our activity and call the removeUpdates method on the LocationManager object.
	 */
	public void onPause() {
		super.onPause(); 
		lm.removeUpdates(this);
	}

	public void onResume() {
		super.onResume();
		lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 6000l, 5.0f, this);
		lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 6000l, 5.0f, this);
	}
	
	/*
	The onLocationChanged method will be called on the registered LocationListener and passed a Location object whenever the location has changed and the change is greater than the distance and time parameters specified in the requestLocationUpdates method.
	The Location object that is passed in has methods available for getting latitude (getLatitude), longitude (getLongitude), altitude (getAltitude), and many more, detailed in the documentation: http://developer.android.com/reference/android/location/Location.html.
	 */

	public void onLocationChanged(Location location) {
		Log.v("LOCATION", "Altitude " + location.getAltitude() + "Supported: " + location.hasAltitude());
		Log.v("LOCATION", "Bearing" + location.getBearing() + "Supported: " + location.hasBearing());
		tv.setText(location.getLatitude() + " " + location.getLongitude()); 
		Log.v("LOCATION", "onLocationChanged: lat=" + location.getLatitude() + ", lon=" + location.getLongitude());
	}

	/*
	The onProviderDisabled method within the registered LocationListener will get called should the provider that is being monitored be disabled by the user.
	*/
	public void onProviderDisabled(String provider) { 
		Log.v("LOCATION", "onProviderDisabled: " + provider);
	}

	/*
	The onProviderEnabled method within the registered LocationListener will get called
	should the provider that is being monitored be enabled by the user.
	*/
	public void onProviderEnabled(String provider) { 
		Log.v("LOCATION", "onProviderEnabled: " + provider);
	}

	/*
	Finally, the onStatusChanged method in the registered LocationListener will be called if the location provider's status changes. There are three constants in LocationProvider that can be tested against the status variable which can be usedto determine what the change that happened is. They are AVAILABLE, which will get called should the provider become available after a period of time being unavailable, TEMPORARILY_UNAVAILABLE, which is just as its name implies, the provider is temporarily unable to be used as it was
	unable to fetch the current location and lastly, OUT_OF_SERVICE, which means that the provider is unable to be used probably due to losing connectivity or signal.
	*/
	public void onStatusChanged(String provider, int status, Bundle extras) { 
		Log.v("LOCATION", "onStatusChanged: " + provider + " status:" + status); 
	
		if (status == LocationProvider.AVAILABLE) {
			Log.v("LOCATION","Provider Available"); 
		} else if (status == LocationProvider.TEMPORARILY_UNAVAILABLE) {
			Log.v("LOCATION","Provider Temporarily Unavailable"); 
		} else if (status == LocationProvider.OUT_OF_SERVICE) {
			Log.v("LOCATION","Provider Out of Service");
		}
	}
}      

	

Map

Google Maps Android API v2

Getting setup to develop an application that uses Google Maps

First, we need to install the Google Play Services SDK. Do to this, open up the Android SDK Manager from within Eclipse and download the "Google Play services" package from the Extras section.

Following that, we need to create a project to house the Google Play services. In Eclipse, goto File, Import, Android, Existing Android Code into Workspace and select the Google Play services package that was just downloaded. It can be found inside your ADT (Android Developer Tools folder), in sdk, extras, google, google_play_services, libproject, google-play-services_lib.

Now we need to request an API key from Google. Using the debug/development key provided by the SDK we can generate a request.

First we hash the debug key (which on mac and linux is located here: ~/.android/debug.keystore)

keytool -v -list -alias androiddebugkey \
-keystore ~/.android/debug.keystore \
-keyalg RSA -storepass android \
-keysize 2048 -validity 10000 \
-keypass android

which outputs (for me):

Certificate fingerprints:
	 MD5:  AB:34:8F:40:CE:80:A3:1E:2B:A8:5C:81:37:C9:88:5C
	 SHA1: 0B:F9:A6:C5:36:1E:78:64:63:30:2B:8C:61:0F:BB:EB:95:E8:4F:BF
	 Signature algorithm name: SHA1withRSA
	 Version: 3

Now that we have that, we can go to the Google Developers Console to create a project and enable the Google Maps Android API v2 API under APIs and auth. Then under Credentials, create a key for Public API Access, select Android key and copy in your SHA-1 fingerprint followed by a semicolon and your mapping application's package name. Mine will be com.mobvcasting.maptest so I'll enter: "0B:F9:A6:C5:36:1E:78:64:63:30:2B:8C:61:0F:BB:EB:95:E8:4F:BF;com.mobvcasting.maptest"

Create our map application

Now that we have an API key, we can create our application. Use the normal methods to create our project in Eclipse but make sure the package name matches what you submitted in the Google Developer Console with your Public API Access request. Then take the following steps:

  • Add the following to your AndroidManifest.xml file, within the application tag:
           <meta-data
                android:name="com.google.android.maps.v2.API_KEY"
                android:value="your_api_key" />
        	<meta-data
    		    android:name="com.google.android.gms.version"
    		    android:value="@integer/google_play_services_version" />
    
    be sure to replace "your_api_key" with what you got from the Google Developers Console.
  • Then in your project properties, add a Library reference to the "google-play-services_lib" that you created previously.
  • Next, you'll want to add the following permissions:
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
    <!-- The following two permissions are not required to use
         Google Maps Android API v2, but are recommended. -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    
  • Finally, add the map to your layout xml, replace everything that is already there with this:
    <?xml version="1.0" encoding="utf-8"?>
    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/map"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:name="com.google.android.gms.maps.MapFragment"/>
    

    Of course, you can do more with the map than just display it at it's default position. Here is an example of moving to a specific Latitude and Longitude:

    package com.mobvcasting.maptest;
    
    import com.google.android.gms.maps.CameraUpdateFactory;
    import com.google.android.gms.maps.GoogleMap;
    import com.google.android.gms.maps.MapFragment;
    import com.google.android.gms.maps.model.LatLng;
    
    import android.os.Bundle;
    import android.app.Activity;
    
    public class MainActivity extends Activity {
    	private GoogleMap map;
    	static final LatLng NEWYORK = new LatLng(40.6700, -73.9400);
    	
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            map = ((MapFragment) getFragmentManager().findFragmentById(R.id.map)).getMap();
            map.moveCamera(CameraUpdateFactory.newLatLngZoom(NEWYORK, 10));
        }
       
    }
    

    More Information: Google Maps Android API v2 - Tutorial and Google Maps Android API v2 Getting Started