Home

Mac OS X 10.10 Juniper VPN

I use a Juniper VPN in order to connect to a remote network. Since upgrading to OS X Yosemite, I found that I could no longer connect to the VPN. Normally, I would launch the application, Network Connect; enter my credentials, and bam! With the new version of OS X, Network Connect would hang when trying to establish a connection to the remote network after enter verifying my credentials. 

The issue lies within Yosemite's driver policies, where all drivers need to be signed now. In Mavericks (10.9) drivers installed to /Libraries/Extensions needed to be signed. If they are installed else where, such as /System/Libraries/Extensions or in the case of Juniper /usr/local/juniper/nc/8.0.7, they were fine. The only way that I was able to find is to turn off this feature, which isn't exactly the best option, but it's the only option until Juniper decides to sign their drivers.

Open up a terminal and run the following command:

sudo nvram boot-args="kext-devel-mode=1"

The command above sets the system into "development mode" where you can "test" the kernel extensions (kext). Again, this is not an ideal solution, but it works. This also works for other drivers as well, such as hard drive TRIM, sound cards, network cards, etc., any driver extensions that isn't currently being signed. To undo this type in the following command in a terminal:

sudo nvram -d boot-args

Android Geofences (Update)

Introduction

A few months ago, I had written about how to setup and monitor geofences. Since then, Google has made changes to their location APIs by removing some of the classes such as the LocationClient class. This change was announced in 2013, but Google's documentation had made no mention of the changes that were to come. At the time of writing, documentation and examples on how to setup and monitor geofences using the new method are scarce.

Requirements

Make sure that you update your SDK packages so that you have Google Play services revision 22. If you are using eclipse and already have Google Play services as  project, you may need to clean and rebuild or reimport the code. If you need help setting up Google Play services, see this.  

Application

The AndroidManifest.xml file doesn't require any changes from my previous entry.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.paulusworld.geofence"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />

    <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="android.permission.WAKE_LOCK" />

    <!--
            Requests address-level location access, which is usually
            necessary for Geofencing
    -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name=".GeofenceIntentService" />

        <!-- Required for Google Maps -->
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
        <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value="YOUR_GOOGLE_MAPS_API_KEY_HERE" />
    </application>

</manifest>

The android.permission.INTERNET and android.permission.ACCESS_NETWORK_STATE are necessary for downloading Google Map data. The android.permission.ACCESS_FINE_LOCATION and com.google.android.providers.gsf.permission.READ_GSERVICES are needed for use with geofences. The permission android.permission.WAKE_LOCK is used for notifications and is not required for geofencing.

We're specifying a service on line 38. By using a service, our app does not need to be running when we enter, exit, or dwell in a geofence.

I've defined some strings in the strings.xml that will be used in the example:

<?xml version="1.0" encoding="utf-8"?>
<resources>
 
    <string name="app_name">Geofence</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
 
    <string name="geofence_transition_notification_title">
        %1$s geofence(s) %2$s
    </string>
    <string name="geofence_transition_notification_text">
        Click notification to return to app
    </string>
 
    <string name="geofence_transition_unknown">Unknown transition</string>
    <string name="geofence_transition_entered">Entered</string>
    <string name="geofence_transition_exited">Exited</string>
    <string name="geofence_transition_dwell">Stop dwelling!</string>
    
    <string name="geofence_intent_service">Geofence Intent Service</string>
</resources>

We'll use a simple layout that uses a MapFragment:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.paulusworld.geofence.MainActivity" >
 
    <fragment
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="45"
        class="com.google.android.gms.maps.SupportMapFragment" />
 
</LinearLayout>

One of the reasons why Google changed the way geofences were handled in code was to make it easier for the developer. If you look at the old way, you will notice that there is a lot less code and logic that is required to make this work. Additionally, I moved the geofence logic into a different class called GeofenceStore. I did this to keep the main activity class as clean as possible.

package com.paulusworld.geofence;

import java.util.ArrayList;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnCameraChangeListener;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity implements OnCameraChangeListener {
	
	/**
	 * Google Map object
	 */
	private GoogleMap mMap;

	/**
	 * Geofence Data
	 */

	/**
	 * Geofences Array
	 */
	ArrayList<Geofence> mGeofences;
	
	/**
	 * Geofence Coordinates
	 */
	ArrayList<LatLng> mGeofenceCoordinates;
	
	/**
	 * Geofence Radius'
	 */
	ArrayList<Integer> mGeofenceRadius;
	
	/**
	 * Geofence Store
	 */
	private GeofenceStore mGeofenceStore;


	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		// Initializing variables
		mGeofences = new ArrayList<Geofence>();
		mGeofenceCoordinates = new ArrayList<LatLng>();
		mGeofenceRadius = new ArrayList<Integer>();
		
		// Adding geofence coordinates to array.
		mGeofenceCoordinates.add(new LatLng(43.042861, -87.911559));
		mGeofenceCoordinates.add(new LatLng(43.042998, -87.909753));
		mGeofenceCoordinates.add(new LatLng(43.040732, -87.921364));
		mGeofenceCoordinates.add(new LatLng(43.039912, -87.897038));
		
		// Adding associated geofence radius' to array.
		mGeofenceRadius.add(100);
		mGeofenceRadius.add(50);
		mGeofenceRadius.add(160);
		mGeofenceRadius.add(160);
		
		// Bulding the geofences and adding them to the geofence array.
		
		// Performing Arts Center
		mGeofences.add(new Geofence.Builder()
				.setRequestId("Performing Arts Center")
				// The coordinates of the center of the geofence and the radius in meters.
				.setCircularRegion(mGeofenceCoordinates.get(0).latitude, mGeofenceCoordinates.get(0).longitude, mGeofenceRadius.get(0).intValue()) 
				.setExpirationDuration(Geofence.NEVER_EXPIRE)
				// Required when we use the transition type of GEOFENCE_TRANSITION_DWELL
				.setLoiteringDelay(30000) 
				.setTransitionTypes(
						Geofence.GEOFENCE_TRANSITION_ENTER
							| Geofence.GEOFENCE_TRANSITION_DWELL
							| Geofence.GEOFENCE_TRANSITION_EXIT).build());
		
		// Starbucks
		mGeofences.add(new Geofence.Builder()
				.setRequestId("Starbucks")
				// The coordinates of the center of the geofence and the radius in meters.
				.setCircularRegion(mGeofenceCoordinates.get(1).latitude, mGeofenceCoordinates.get(1).longitude, mGeofenceRadius.get(1).intValue()) 
				.setExpirationDuration(Geofence.NEVER_EXPIRE)
				// Required when we use the transition type of GEOFENCE_TRANSITION_DWELL
				.setLoiteringDelay(30000) 
				.setTransitionTypes(
						Geofence.GEOFENCE_TRANSITION_ENTER
							| Geofence.GEOFENCE_TRANSITION_DWELL
							| Geofence.GEOFENCE_TRANSITION_EXIT).build());
		
		// Milwaukee Public Museum
		mGeofences.add(new Geofence.Builder()
				.setRequestId("Milwaukee Public Museum")
				// The coordinates of the center of the geofence and the radius in meters.
				.setCircularRegion(mGeofenceCoordinates.get(2).latitude, mGeofenceCoordinates.get(2).longitude, mGeofenceRadius.get(2).intValue()) 
				.setExpirationDuration(Geofence.NEVER_EXPIRE)
				.setTransitionTypes(
						Geofence.GEOFENCE_TRANSITION_ENTER
							| Geofence.GEOFENCE_TRANSITION_EXIT).build());
		
		// Milwaukee Art Museum
		mGeofences.add(new Geofence.Builder()
				.setRequestId("Milwaukee Art Museum")
				// The coordinates of the center of the geofence and the radius in meters.
				.setCircularRegion(mGeofenceCoordinates.get(3).latitude, mGeofenceCoordinates.get(3).longitude, mGeofenceRadius.get(3).intValue()) 
				.setExpirationDuration(Geofence.NEVER_EXPIRE)
				.setTransitionTypes(
						Geofence.GEOFENCE_TRANSITION_ENTER
							| Geofence.GEOFENCE_TRANSITION_EXIT).build());
		
		// Add the geofences to the GeofenceStore object.
		mGeofenceStore = new GeofenceStore(this, mGeofences);

	}

	@Override
	protected void onStart() {
		super.onStart();
	}

	@Override
	protected void onStop() {
		mGeofenceStore.disconnect();
		super.onStop();
	}

	@Override
	protected void onResume() {
		super.onResume();
		if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS) {
			setUpMapIfNeeded();
		} else {
			GooglePlayServicesUtil.getErrorDialog(
					GooglePlayServicesUtil.isGooglePlayServicesAvailable(this),
					this, 0);
		}
	}

	private void setUpMapIfNeeded() {
		// Do a null check to confirm that we have not already instantiated the
		// map.
		if (mMap == null) {
			// Try to obtain the map from the SupportMapFragment.
			mMap = ((SupportMapFragment) getSupportFragmentManager()
					.findFragmentById(R.id.map)).getMap();

			// Check if we were successful in obtaining the map.
			if (mMap != null) {
				setUpMap();
			}
		}
	}

	/**
	 * This is where we can add markers or lines, add listeners or move the
	 * camera. In this case, we just add a marker near Africa.
	 * <p/>
	 * This should only be called once and when we are sure that {@link #mMap}
	 * is not null.
	 */
	private void setUpMap() {
		// Centers the camera over the building and zooms int far enough to
		// show the floor picker.
		mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(
				43.039634, -87.908395), 14));
		
		// Hide labels.
		mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
		mMap.setIndoorEnabled(false);
		mMap.setMyLocationEnabled(true);
		
		mMap.setOnCameraChangeListener(this);

	}

	@Override
	public void onCameraChange(CameraPosition position) {
		// Makes sure the visuals remain when zoom changes.
		for(int i = 0; i < mGeofenceCoordinates.size(); i++) {
			mMap.addCircle(new CircleOptions().center(mGeofenceCoordinates.get(i))
					.radius(mGeofenceRadius.get(i).intValue())
					.fillColor(0x40ff0000)
					.strokeColor(Color.TRANSPARENT).strokeWidth(2));
		}
	}
}

 

package com.paulusworld.geofence;

import java.util.ArrayList;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingRequest;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;

public class GeofenceStore implements ConnectionCallbacks,
		OnConnectionFailedListener, ResultCallback<Status>, LocationListener {

	private final String TAG = this.getClass().getSimpleName();

	/**
	 * Context
	 */
	private Context mContext;
	
	/**
	 * Google API client object.
	 */
	private GoogleApiClient mGoogleApiClient;
	
	/**
	 * Geofencing PendingIntent
	 */
	private PendingIntent mPendingIntent;
	
	/**
	 * List of geofences to monitor.
	 */
	private ArrayList<Geofence> mGeofences;
	
	/**
	 * Geofence request.
	 */
	private GeofencingRequest mGeofencingRequest;
	
	/**
	 * Location Request object.
	 */
	private LocationRequest mLocationRequest;

	/**
	 * Constructs a new GeofenceStore. 
	 * 
	 * @param context The context to use.
	 * @param geofences List of geofences to monitor.
	 */
	public GeofenceStore(Context context, ArrayList<Geofence> geofences) {
		mContext = context;
		mGeofences = new ArrayList<Geofence>(geofences);
		mPendingIntent = null;

		// Build a new GoogleApiClient, specify that we want to use LocationServices
		// by adding the API to the client, specify the connection callbacks are in 
		// this class as well as the OnConnectionFailed method. 
		mGoogleApiClient = new GoogleApiClient.Builder(context)
				.addApi(LocationServices.API).addConnectionCallbacks(this)
				.addOnConnectionFailedListener(this).build();

		// This is purely optional and has nothing to do with geofencing. 
		// I added this as a way of debugging.
		// Define the LocationRequest.
		mLocationRequest = new LocationRequest();
		// We want a location update every 10 seconds.
		mLocationRequest.setInterval(10000);
		// We want the location to be as accurate as possible.
		mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
		
		mGoogleApiClient.connect();
	}

	@Override
	public void onResult(Status result) {
		if (result.isSuccess()) {
			Log.v(TAG, "Success!");
		} else if (result.hasResolution()) {
			// TODO Handle resolution
		} else if (result.isCanceled()) {
			Log.v(TAG, "Canceled");
		} else if (result.isInterrupted()) {
			Log.v(TAG, "Interrupted");
		} else {

		}

	}

	@Override
	public void onConnectionFailed(ConnectionResult connectionResult) {
		Log.v(TAG, "Connection failed.");
	}

	@Override
	public void onConnected(Bundle connectionHint) {
		// We're connected, now we need to create a GeofencingRequest with
		// the geofences we have stored.
		mGeofencingRequest = new GeofencingRequest.Builder().addGeofences(
				mGeofences).build();
		
		mPendingIntent = createRequestPendingIntent();
		
		// This is for debugging only and does not affect
		// geofencing.
		LocationServices.FusedLocationApi.requestLocationUpdates(
				mGoogleApiClient, mLocationRequest, this);
		
		// Submitting the request to monitor geofences.
		PendingResult<Status> pendingResult = LocationServices.GeofencingApi
				.addGeofences(mGoogleApiClient, mGeofencingRequest,
						mPendingIntent);
		
		// Set the result callbacks listener to this class.
		pendingResult.setResultCallback(this);
	}

	@Override
	public void onConnectionSuspended(int cause) {
		Log.v(TAG, "Connection suspended.");
	}

	/**
	 * This creates a PendingIntent that is to be fired when geofence transitions
	 * take place. In this instance, we are using an IntentService to handle the
	 * transitions.
	 * 
	 * @return A PendingIntent that will handle geofence transitions.
	 */
	private PendingIntent createRequestPendingIntent() {
		if (mPendingIntent == null) {
			Log.v(TAG, "Creating PendingIntent");
			Intent intent = new Intent(mContext, GeofenceIntentService.class);
			mPendingIntent = PendingIntent.getService(mContext, 0, intent,
					PendingIntent.FLAG_UPDATE_CURRENT);
		}

		return mPendingIntent;
	}

	@Override
	public void onLocationChanged(Location location) {

		Log.v(TAG, "Location Information\n" 
				+ "==========\n"
				+ "Provider:\t" + location.getProvider() + "\n"
				+ "Lat & Long:\t" + location.getLatitude() + ", "
				+ location.getLongitude() + "\n"
				+ "Altitude:\t" + location.getAltitude() + "\n"
				+ "Bearing:\t" + location.getBearing() + "\n"
				+ "Speed:\t\t" + location.getSpeed() + "\n"
				+ "Accuracy:\t" + location.getAccuracy() + "\n");
	}
}

The GeofenceIntentService is a basic class that handles Intent broadcasts from the LocationServices API. The sendNotification and getTriggeringGeofences are just additional logic code that is executed when a Geofence transition is broadcast.

package com.paulusworld.geofence;

import java.util.List;

import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingEvent;

import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import android.util.Log;

public class GeofenceIntentService extends IntentService {

	private final String TAG = this.getClass().getCanonicalName();
	
	public GeofenceIntentService() {
		super("GeofenceIntentService");
		Log.v(TAG, "Constructor.");
	}

	public void onCreate() {
		super.onCreate();
		Log.v(TAG, "onCreate");
	}
	
	public void onDestroy() {
		super.onDestroy();
		Log.v(TAG, "onDestroy");
	}
	
	@Override
	protected void onHandleIntent(Intent intent) {
		GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
		Log.v(TAG, "onHandleIntent");
		if(!geofencingEvent.hasError()) {
			int transition = geofencingEvent.getGeofenceTransition();
			String notificationTitle;
			
			switch(transition) {
			case Geofence.GEOFENCE_TRANSITION_ENTER:
				notificationTitle = "Geofence Entered";
				Log.v(TAG, "Geofence Entered");
				break;
			case Geofence.GEOFENCE_TRANSITION_DWELL:
				notificationTitle = "Geofence Dwell";
				Log.v(TAG, "Dwelling in Geofence");
				break;
			case Geofence.GEOFENCE_TRANSITION_EXIT:
				notificationTitle = "Geofence Exit";
				Log.v(TAG, "Geofence Exited");
				break;
			default:
				notificationTitle = "Geofence Unknown";
			}
			
			sendNotification(this, getTriggeringGeofences(intent), notificationTitle);
		}
	}

	private void sendNotification(Context context, String notificationText,
			String notificationTitle) {

		PowerManager pm = (PowerManager) context
				.getSystemService(Context.POWER_SERVICE);
		PowerManager.WakeLock wakeLock = pm.newWakeLock(
				PowerManager.PARTIAL_WAKE_LOCK, "");
		wakeLock.acquire();

		NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(
				context).setSmallIcon(R.drawable.ic_launcher)
				.setContentTitle(notificationTitle)
				.setContentText(notificationText)
				.setDefaults(Notification.DEFAULT_ALL).setAutoCancel(false);

		NotificationManager notificationManager = (NotificationManager) context
				.getSystemService(Context.NOTIFICATION_SERVICE);
		notificationManager.notify(0, notificationBuilder.build());

		wakeLock.release();
	}
	
	private String getTriggeringGeofences(Intent intent) {
		GeofencingEvent geofenceEvent = GeofencingEvent.fromIntent(intent);
		List<Geofence> geofences = geofenceEvent
				.getTriggeringGeofences();
		
		String[] geofenceIds = new String[geofences.size()];

		for (int i = 0; i < geofences.size(); i++) {
			geofenceIds[i] = geofences.get(i).getRequestId();
		}

		return TextUtils.join(", ", geofenceIds);
	}
}

In order to get Geofence information from the intent received in the IntentService, we create a GeofencingEvent by calling GeofencingEvent.fromIntent(Intent intent) on line 39. On line 41, we make sure there are no errors before getting the transition on line 42 by calling the getGeofenceTransition function. From that point, the rest of the function is self explanatory and the rest of the class is beyond the scope of Geofencing.

There is one important thing to remember when working with geofences, transition broadcasts won't be fired if the user is not completely in the geofence. Accuracy is also taken into account when determining if a user has entered, exitied, or is dwelling. For example, if a geofence has a diameter of 100 meters and your accuracy is 150 meteres even if you are shown as being smack in the center of the geofence, no transitions will be fired because Google isn't sure if you are in the geofence or not.  

Android Notifications

Introduction

Notifications are those little badges that appear at the top of your Android device's screen. Creating the notifications are pretty easy and straight forward and can be extended to provide actions opposed to a simple message notification. The application in this post is a simple application which has a single button that fires off a notification.

Application

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.paulusworld.notificationexample"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".NotifiedActivity"
            android:label="@string/title_activity_notified"
            android:parentActivityName=".MainActivity" >
            <meta-data 
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".MainActivity" />
        </activity>
    </application>

</manifest>

According to Google, when you start an Activity form a notification, you must preserve the user's expected navigation experience. This is only true if the Activity you are starting from the notification is a part of the application's normal workflow. If this is a "special" Activity -- an Activity that is extending the notification by providing information that would be impossible to have in an Notification, then you do not need to preserve the navigation.

The first step we must take to preserve the navigation stack is to define the application hierarchy. For Android 4.0.3 and earlier you do this by adding the meta-data tag (lines 28 - 30) and for Android 4.1 and later, use the android:parentActivityName attribute, found on line 27. The second step is to create a back stack based on the Intent that starts the Activity, which is done in code and found in the MainActivity.java file.  

package com.paulusworld.notificationexample;
 
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.view.View;
 
public class MainActivity extends Activity {
 
    private final String NOTIFICATION_TAG = "NotificationExample";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
     
    public void sendNotification(View view) {
        Intent resultIntent = new Intent(this, NotifiedActivity.class);
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
         
        // Adds the back stack -- defined in the manifest file.
        stackBuilder.addParentStack(NotifiedActivity.class);
        // Adds the intent to the top of the stack.
        stackBuilder.addNextIntent(resultIntent);
        // Create a PendingIntent that contains the back stack.
        PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
         
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
            // Set appropriate defaults for the notification light, sound,
            // and vibration.
            .setDefaults(Notification.DEFAULT_ALL)
            // Set the required fields.
            .setSmallIcon(R.drawable.ic_launcher)
            .setContentTitle("Notification Title")
            .setContentText("Notification Message")
            // The following are all optional.
            // Use a default priority; recognized by Android 4.1+
            .setPriority(Notification.PRIORITY_DEFAULT)
            // Provide a large icon, shown with the notification
            // in the notification drawer on devices running Android
            // 3.0+
            .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher))
            // Show some preview text.
            .setTicker("Ticker!")
            // Show a number -- this is typically used to show the
            // number of notifications.
            .setNumber(1)
            // If the notification relates to a past or upcoming event
            // You can specify the time in which the notification
            // will appear to have been fired. The following is an
            // example of how to set the notification time. If you
            // don't want to specify a time, then the current time
            // will be used.
            .setWhen(System.currentTimeMillis()) 
            // Set the Intent for when a user touches the notification.
            .setContentIntent(resultPendingIntent)
            // With Android 4.1+ you can add more actions to a notifications.
            // For example, the gmail app. When an email is received, you have
            // the options of replying or deleting the email right from the
            // Notification drawer.
            .addAction(
                    R.drawable.ic_launcher, 
                    "Action", 
                    null)
            // Automatically dismiss the notification when the user taps on it.
            .setAutoCancel(true);
         
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
         
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
            notificationManager.notify(NOTIFICATION_TAG, 0, builder.build());
        } else {
            notificationManager.notify(NOTIFICATION_TAG.hashCode(), builder.build());
        }
    }
}

The NotificationCompat.Builder has several methods that set various aspects of the notification before actually being built when the build() function is called. The first four calls are required in order for the notification to be successfully built; setDefaults(), setSmallIcon(), setContentTitle(), and setContentText().

The setDefaults() tells the notification manager what to use when the notification is sent. DEFAULT_SOUND, DEFAULT_VIBRATE, DEFAULT_LIGHTS, or DEFAULT_ALL.

setSmallIcon() assigns the icon that will be displayed in the notification bar.

the setContentTitle() and setContentText() assigns the title and message, respectively.

setPriority() is recognized by Android 4.1+. This indicates how important the notification is. Low-priority notifications may be hidden from the user in certain situations, while higher-priority notifications may interrupt the user.

setLargeIcon() is the icon that is used when the ticker is displayed. This is also the icon that is used in the notification itself.

The setTicker() function takes a string and uses that in the notification bar at the top of the screen to give the user a quick preview of the notification.

setNumber() simply shows a number. This number usually indicates the number of notifications.

setWhen() allows you to specify a time in which you want to report to the user. Setting this is useful when the notification relates to a past or future event.

Although the setContentIntent() function is optional, it's a good idea set this. This function takes a PendingIntent object as it's only argument.

With Android 4.1+ you can add more actions to a notification. Using the GMail application as an example. When you receive an email, a notification is displayed. Pulling down the Notification Drawer, you'll see the expanded notification along with additional actions; Reply and Delete. In the example above, the third parameter is null, which should be a PendingIntent object. For simplicity, we just left it as null.

Setting the setAutoCancel() to true will clear the notification when the user taps on it. 

There are other attribtues you can set on the notification such as the color of the light and how fast it will flash, the sound file to use, etc. You can view the list of functions the NotificationCompat.Builder provides here.

 

In the MainActivity class, we didn't implement an OnClickListener, but we defined the button action within the layout. android:onClick="@string/sendNotification"

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="${relativePackage}.${activityClass}" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:onClick="@string/sendNotification"
        android:text="@string/button_notify" />

</RelativeLayout>
package com.paulusworld.notificationexample;

import android.app.Activity;
import android.os.Bundle;

public class NotifiedActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_notified);
	}
}

The NotifiedActivity is nothing special and only displayed when the user taps on the notification.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/notified"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</LinearLayout>

Finally, the strings.xml file.

<?xml version="1.0" encoding="utf-8"?>
<resources>
 
    <string name="app_name">NotificationExample</string>
    <string name="button_notify">Notify</string>
    <string name="message_notified">You have acknowledged the notification.</string>
    <string name="title_activity_notified">Notified</string>
    <string name="sendNotification">sendNotification</string>
    <string name="notified">You have just been notified!</string>
 
</resources>

 

Pages