Native Android

Quick Links

Starting your application – Where to begin

If you’re new to Android development, you’ll need to setup Eclipse, Java, and download the Android SDK. Many resources already exist that show how to complete this setup,  with a good example being on the Android Developers page. After you’ve created your first “hello world” application, you’ll be able to verify everything is working on your emulator/device.

Note: Push notifications only work on a device that is registered with a valid email account. If you’re using an emulator, you’ll need to use Google API (2.2 or later) and be sure to add an account. If you’re using an android device, you’ll need to verify you’re running on a system that uses 2.2 or later.

The following video details the General Architecture of an Android application:

Android uses Activities to show each page of an application. Example Activities could be as follows:

MainApplication.java –  Runs each time the application starts up
MainActivity.java – First Activity to launch
SomeActivity.java – You can add additional activities as needed

List each of these Activities in your AndroidManifest.xml
Create .xml files such as main.xml or some.xml in the layout folder.

GenericActivity.java – I noticed that I needed to use some of the same methods for all of my Activities. At first I tried to implement all of these methods in each Activity, but then I decided to create a that had a generic version of all the methods I kept using over and over. (methods such as isActivityVisible, getActivityVisible, getChatWithAccount, Preferences)

When you want to change activities, change them by creating a new childActivityIntent as follows:

        Intent childActivityIntent = new Intent(getApplicationContext(), SomeActivity.class);
	startActivity(childActivityIntent);

Push Notifications

After creating an application, you’ll want to add Push functionality. We recommend reading our overview page for general architecture regarding push.

Several push providers exist that aim to simplify the process, but for this tutorial, we show how to use Urban Airship. Receiving a notification looks like the following:

Implement Push using Urban Airship:

The Main class you write will look similar to the following code snippet.
MainApplication.java
/**
 * Called when application is launched, handles Urban Airship component
 * @author Marissa.Nielsen
 *
 */
public class MainApplication extends Application {

	public static final String DEFAULT_PREFS_NAME = "MyPrefs";
    /** Called when the activity is first created. */
    public void onCreate() {
        super.onCreate();

        // Enables push notifications, automatically searches assets/airshipconfig.properties for defaults
        UAirship.takeOff(this);
        PushManager.enablePush();

        // We want to handle push notifications in a special way, this allows for customization
        PushManager.shared().setNotificationBuilder(new NotificationHandler());
        PushManager.shared().setIntentReceiver(IntentReceiver.class);

        // UAirship creates a pushId for the device, and this pushId needs to be sent to your application server
        PushPreferences prefs = PushManager.shared().getPreferences();
        SharedPreferences mPreferences = getApplicationContext().getSharedPreferences(DEFAULT_PREFS_NAME, 0);
		SharedPreferences.Editor editor = mPreferences.edit();
		editor.putString("PushId", prefs.getPushId());
		editor.commit();
        Logger.info("PushId: " + prefs.getPushId());
    }

    public void onStop() {
    	UAirship.land();
    }

}
Note: If you’re going to overwrite Urban Airship’s jar, then you’ll need to tell Urban Airship as shown in lines 18 and 19. One convenience of using Urban Airship is that the registration of the device and application with GCM is taken care for you on the UAirship.takeOff(this) on line 14. The pushid that is created may be used by your application server as part of credentials, so we store it in Preferences so that it can be accessed in the application’s activities.

How to Handle a Push

After completing these steps, you can send a push to a device, but nothing happens when you click on the notification. Implementing these features requires customization on the notification receiving end of the process:

When the application receives a push, Urban Airship’s jar hears the push. Urban Airship’s jar calls the Notification Builder to create a status bar notification. This class can be overwritten as show in NotificationHandler.java below so that in the case that the application is open and in the correct context, a refresh is called rather than a status bar notification (for example, in a chat application if the friend you’re chatting with has sent a new message, you want to refresh with that message rather than creating a new status bar notification).

Notification Handler.java

public class NotificationHandler extends BasicPushNotificationBuilder {

	public static final String PREFS_NAME = "MyPrefs";
	private static Map<Integer, ConversationListener> conversationListeners = new TreeMap<Integer, ConversationListener>();

	public Notification buildNotification(String alert, Map<String, String> Extras) {
		Log.d("NotificationBuilder", "Received notification: " + alert);
		printMap(Extras);

		// Customize this section to handle cases
		if(!GenericActivity.isActivityVisible()) { 					// If the application is in the background
			return super.buildNotification(alert, Extras);
		} else if (GenericActivity.getActivityVisible().equals("MainActivity")) {
			ConversationListener listener = conversationListeners.get(0);
			listener.reloadConversation(); 						// Refresh
		} else { 									// Regular Status Bar notification
			return super.buildNotification(alert, Extras);
		}
	}

    public static void addConversationListener(ConversationListener listener) {
    	conversationListeners.put(new Integer(listener.getConversationId()), listener);
    }

    public void printMap(Map<String, String> map){
    	for (String key : map.keySet()) {
    		Log.d("NotificationBuilder", "key/value: " + key + "/"+map.get(key));
        }
    }
}

If a status bar notification is created, some sort of action will need to occur when the user clicks on it. This action is completed using a class that extends BroadCast receiver, as shown by IntentReceiver.java below. By overwriting the onRecieve method 0f this class, the developer can specify which activity to open and any extra attributes of the message (for example, in a chat application, the developer can specify to open the ConversationActivity.java class and include the account number of the person the user is chatting with).

IntentReceiver.java
/**
 *
 * @author Marissa.Nielsen
 *
 */
public class IntentReceiver extends BroadcastReceiver {

	private static final String TAG = IntentReceiver.class.getSimpleName();
	private static int id;

	@Override
	public void onReceive(Context context, Intent intent) {
		Log.i(TAG, "Received intent: " + intent.toString());
		Log.i(TAG, "ActivityVisible: " + GenericActivity.isActivityVisible());

		String action = intent.getAction();
		if (action.equals(PushManager.ACTION_PUSH_RECEIVED)) {
			id = intent.getIntExtra(PushManager.EXTRA_NOTIFICATION_ID, 0);
			Log.i(TAG, "Received push notification. Alert: "
					+ intent.getStringExtra(PushManager.EXTRA_ALERT)
					+ " [NotificationID="+id+"]");
			logPushExtras(intent);
		}
		else if (action.equals(PushManager.ACTION_NOTIFICATION_OPENED)) {
			Log.i(TAG, "User clicked notificaiton. Message: "
					+ intent.getStringExtra(PushManager.EXTRA_ALERT));
			logPushExtras(intent);
			Intent launch = new Intent (Intent.ACTION_MAIN);

			// Changes the class to implement
			Class toImplement = ConversationActivity.class; //<? extends Activity>
			if (intent.getExtras().getString("fromAccount").equals("" + -1)) {
				toImplement = MessageActivity.class;
			}
			launch.setClass(UAirship.shared().getApplicationContext(), toImplement);

			launch.putExtras(intent.getExtras());
			Log.i(TAG, "fromAccount: " + intent.getStringExtra("fromAccount"));
			launch.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			UAirship.shared().getApplicationContext().startActivity(launch);

		} else if (action.equals(PushManager.ACTION_REGISTRATION_FINISHED)) {
			 Log.i(TAG, "Registration complete. APID:" + intent.getStringExtra(PushManager.EXTRA_APID)
	                    + ". Valid: " + intent.getBooleanExtra(PushManager.EXTRA_REGISTRATION_VALID, false));
		}
	}

	private String getKey(Intent intent, String action) {
		Set<String> keys = intent.getExtras().keySet();
		for (String key : keys) {
			if (key.equals(action)) {
				return intent.getStringExtra(key);
			}
		}
		return null;
	}

	private void logPushExtras(Intent intent) {
		Set<String> keys = intent.getExtras().keySet();
		for (String key : keys) {
			//ignore standard C2DM extra keys
			List<String> ignoredKeys = (List<String>)Arrays.asList(
					"collapse_key",
					"from",
					PushManager.EXTRA_NOTIFICATION_ID,
					PushManager.EXTRA_PUSH_ID,
					PushManager.EXTRA_ALERT);
			if (ignoredKeys.contains(key)) {
				continue;
			}
			Log.i(TAG, "Push Notification Extra: ["+key+" : " + intent.getStringExtra(key) + "]");
		}
	}
}

Additional Sample Code

This class holds all the shared methods between all the activities. Make all of your other activities extend this class.
GenericActivity.java
/**
 * GenericActivity is a super class created to
 * 		track whether the application is visible,
 *		know which activity is visible if the application is visible, and
 *		set a SharedPreferences to store data transfered within the application
 *
 * @author Marissa.Nielsen
 *
 */
public class GenericActivity extends Activity  {

	/**
	 * Activity LifeCycle:
	 * http://developer.android.com/reference/android/app/Activity.html
	 * When the application is created, resumed, or paused,
	 * 		we track if the application is in the background and which activity is visible
	 */
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		System.out.println("Hello World2");
		setActivityName(this.getClass().getName());
	}

	@Override
	protected void onResume() {
	  super.onResume();
	  activityResumed();
	  setActivityName(this.getClass().getName());
	}

	@Override
	protected void onPause() {
	  super.onPause();
	  activityPaused();
	}

	/**
	 * Allows shared storage between all the Activities
	 * @return
	 */
	protected SharedPreferences getPreferences() {
		if(mPreferences == null) {
			mPreferences = this.getSharedPreferences(PREFS_NAME, 0);
		}
		return mPreferences;
	}

	public static final String PREFS_NAME = "MyPrefs";
	private static SharedPreferences mPreferences = null;

    // Who am I in a conversation with?
    public static long getChatWithAccount() {
    	return chatWithAccount;
    }
    public static void setChatWithAccount(long cwa) {
    	chatWithAccount = cwa;
    }
    private static long chatWithAccount = 0;

    // Which Activity is Visible?
    public static String getActivityVisible() {
    	return activityName;
    }
    public static void setActivityName(String an) {
    	// class name: com.parivedasolutions.nativefinslap.activities.GenericActivity
    	// is shortened to: GenericActivity
    	String[] a; a = an.split("\\."); an = a[a.length-1];
    	activityName = an;
    }
    private static String activityName;

    // Is the Activity Visible?
    public static boolean isActivityVisible() {
        return activityVisible;
      }
      public static void activityResumed() {
        activityVisible = true;
      }
      public static void activityPaused() {
        activityVisible = false;
      }
      private static boolean activityVisible;

}

Here’s an example of a sample Activity

/**
 * Main UI
 * @author Marissa.Nielsen
 *
 */
public class MainActivity extends GenericActivity implements ConversationListener {

	private ListView convoListView;
	private List<Conversation> conversationList;
	private String authToken;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Logout Button
        Button logoutbtn = (Button)findViewById(R.id.button2);
        logoutbtn.setOnClickListener(new OnClickListener() {
			public void onClick(View arg0) {
				logout(); // logout of the application
				finish(); // exit activity
			}
		});

        // Add Conversation Button
        Button addconvobtn = (Button)findViewById(R.id.button1);
        addconvobtn.setOnClickListener(new OnClickListener() {
			public void onClick(View arg0) {
				Intent childActivityIntent = new Intent(getApplicationContext(),
						ContactsActivity.class);
				startActivity(childActivityIntent);
			}
		});

        // Title
        TextView textview1 = (TextView)findViewById(R.id.tvMain);
        textview1.setText("FinSlap");

		// Shared Preferences
    	SharedPreferences.Editor editor = getPreferences().edit();
		authToken = getPreferences().getString("AuthToken", null);

		if (loadConversations() == false) { // check the token, if you can't login, then login
			editor.putString("AuthToken", null);
			editor.commit();
		}

		if (authToken == null) { // if auth-token is no good, ask user to log in
			Intent childActivityIntent = new Intent(getApplicationContext(),
					LoginActivity.class);
			startActivity(childActivityIntent);
		}

		NotificationHandler.addConversationListener(this);
	}
You cannot simply copy and paste a manifest file, but must change the ‘com.parivedasolutions.nativefinslap’ to your package
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.parivedasolutions.nativefinslap"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk android:minSdkVersion="8" />

        <!-- REQUIRED -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.VIBRATE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.BROADCAST_STICKY" />

    <!-- REQUIRED for C2DM  -->
    <!-- Only this application can receive the messages and registration result -->
    <permission android:name="com.parivedasolutions.nativefinslap.permission.C2D_MESSAGE" android:protectionLevel="signature" />
    <uses-permission android:name="com.parivedasolutions.nativefinslap.permission.C2D_MESSAGE" />

    <!-- This app has permission to register and receive message -->
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:name=".MainApplication"
        android:allowClearUserData="true"
        android:enabled="true">
        <activity
            android:name=".activities.SomeActivity"
            android:theme="@android:style/Theme.NoTitleBar"
            android:configChanges="orientation|keyboardHidden"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <activity
            android:name=".activities.MainActivity"
            android:theme="@android:style/Theme.NoTitleBar"
            android:configChanges="orientation|keyboardHidden"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

                <!-- REQUIRED -->
        <receiver android:name="com.urbanairship.CoreReceiver"></receiver>

            <!-- REQUIRED for C2DM -->
        <receiver android:name="com.urbanairship.push.c2dm.C2DMPushReceiver"
                android:permission="com.google.android.c2dm.permission.SEND">
          <!-- Receive the actual message -->
          <intent-filter>
              <action android:name="com.google.android.c2dm.intent.RECEIVE" />
              <category android:name="com.parivedasolutions.nativefinslap" />
          </intent-filter>
          <!-- Receive the registration id -->
          <intent-filter>
              <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
              <category android:name="com.parivedasolutions.nativefinslap" />
          </intent-filter>
        </receiver>

		<!-- REQUIRED -->
        <service android:name="com.urbanairship.push.PushService" />

        <!-- OPTIONAL, if you want to receive push, push opened and registration completed intents -->
        <receiver android:name=".push.IntentReceiver" />

    </application>
</manifest>

Github: https://github.com/ParivedaInternsSeattle/PushNotificationSolutionAccelerator

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s