Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ hermes/build
hermesexample/build
hermes/hermes.iml
hermesexample/hermesexample.iml
gradle.properties
gradle.properties
gradlew.bat
9 changes: 5 additions & 4 deletions hermes/build.gradle
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
apply plugin: 'com.android.library'

android {
compileSdkVersion 21
buildToolsVersion "21.1.2"
compileSdkVersion 22
buildToolsVersion "22.0.1"

defaultConfig {
minSdkVersion 9
targetSdkVersion 21
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
Expand All @@ -20,7 +20,8 @@ android {

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.android.gms:play-services-gcm:7.0.0'
compile 'com.android.support:support-v4:22.1.1'
compile 'com.google.android.gms:play-services-gcm:7.3.0'
}

apply from: "../maven.gradle"
6 changes: 1 addition & 5 deletions hermes/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@
<application
android:allowBackup="true"
android:label="@string/app_name" >
<!-- TODO: Move to HermesBroadcastReceiver -->
<receiver
android:name=".receivers.RebootReceiver"
android:enabled="true"
android:exported="true" >
</receiver>
<receiver
android:name=".receivers.HermesBroadcastReceiver"
android:enabled="true"
android:exported="true" >
</receiver>
<receiver
android:name=".receivers.ExponentialBackoffReceiver"
android:enabled="true"
Expand Down
38 changes: 24 additions & 14 deletions hermes/src/main/java/in/raveesh/hermes/Hermes.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
*/
public class Hermes {

public static final String TAG = "Hermes";

public final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
public static final String PROPERTY_REG_ID = "registration_id";
private static final String PROPERTY_APP_VERSION = "appVersion";
Expand All @@ -46,15 +44,15 @@ public static boolean checkPlayServices(Context context) {
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
if (resultCode != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
Log.d(TAG, "Google Play Services is not available, but the issue is User Recoverable");
Log.d("Hermes", "Google Play Services is not available, but the issue is User Recoverable");
if (context instanceof Activity) {
GooglePlayServicesUtil.getErrorDialog(resultCode, (Activity) context, PLAY_SERVICES_RESOLUTION_REQUEST).show();
}
} else {
/**
* TODO: Handle all issue related to non User recoverable error
*/
Log.e(TAG, "Non user recoverable error while checking for Play Services");
Log.d("Hermes", "Non user recoverable error while checking for Play Services");
}
return false;
}
Expand All @@ -75,10 +73,12 @@ public static void register(Context context, String senderID, RegistrationCallba
CALLBACK = callback;
}

Log.d("Hermes", "Registering... ");

SENDER_ID = senderID;
if (checkPlayServices(context)) {
gcm = GoogleCloudMessaging.getInstance(context);
regID = getRegistrationId(context);
regID = getRegIdFromSharedPrefs(context);
if (regID.isEmpty()) {
registerInBackground(context);
}
Expand All @@ -99,20 +99,22 @@ else if (callback != null){
* @return registration ID, or empty string if there is no existing
* registration ID.
*/
private static String getRegistrationId(Context context) {
private static String getRegIdFromSharedPrefs(Context context) {
final SharedPreferences prefs = getGCMPreferences(context);
String registrationId = prefs.getString(PROPERTY_REG_ID, "");
if (registrationId.isEmpty()) {
Log.i(TAG, "Registration not found.");
if (registrationId == null || registrationId.isEmpty()) {
Log.d("Hermes", "Registration not found.");
return "";
}
Log.d("Hermes", "Got Reg Id from prefs");

// Check if app was updated; if so, it must clear the registration ID
// since the existing registration ID is not guaranteed to work with
// the new app version.
int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
int currentVersion = getAppVersion(context);
if (registeredVersion != currentVersion) {
Log.i(TAG, "App version changed.");
Log.d("Hermes", "App version changed.");
return "";
}
return registrationId;
Expand Down Expand Up @@ -156,17 +158,19 @@ private static void registerInBackground(final Context context) {
if (CALLBACK != null){
CALLBACK.registrationProcessStarted();
}
new AsyncTask() {
Log.d("Hermes", "Registers the application with GCM servers asynchronously");

new AsyncTask<Object, Void, String>() {
@Override
protected Object doInBackground(Object[] params) {
protected String doInBackground(Object[] params) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regID = gcm.register(SENDER_ID);
msg = "Device registered, registration ID=" + regID;

Log.d("Hermes", "From Async - "+msg);
// You should send the registration ID to your server over HTTP,
// so it can use GCM/HTTP or CCS to send messages to your app.
// The request to your server should be authenticated if your app
Expand All @@ -179,11 +183,17 @@ protected Object doInBackground(Object[] params) {

// Persist the registration ID - no need to register again.
storeRegistrationId(context, regID);
if (CALLBACK != null){
CALLBACK.registrationComplete(regID);
}
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
// If there is an error, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off.
ExponentialBackoffReceiver.attemptRegistration(context,
Hermes.getDelay(),
Hermes.getSenderId());
}
return msg;
}
Expand All @@ -199,11 +209,11 @@ protected Object doInBackground(Object[] params) {
private void storeRegistrationId(Context context, String regId) {
final SharedPreferences prefs = getGCMPreferences(context);
int appVersion = getAppVersion(context);
Log.i(TAG, "Saving regId on app version " + appVersion);
Log.d("Hermes", "Saving regId on app version " + appVersion);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(PROPERTY_REG_ID, regId);
editor.putInt(PROPERTY_APP_VERSION, appVersion);
editor.commit();
editor.apply();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static void attemptRegistration(Context context, int time, String sender_
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, time, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
manager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + time, pendingIntent);
Log.d(Hermes.TAG, "Exponential Backoff Set for " + time);
Log.d("Hermes", "Exponential Backoff Set for " + time);
if (Hermes.CALLBACK != null){
Hermes.CALLBACK.setExponentialBackoff(time);
}
Expand All @@ -37,7 +37,7 @@ public void onReceive(Context context, Intent intent) {
if (Hermes.CALLBACK != null){
Hermes.CALLBACK.backoffComplete(backedOff);
}
Log.d(Hermes.TAG, "Exponential backoff complete after "+ backedOff);
Log.d("Hermes", "Exponential backoff complete after " + backedOff);
Hermes.register(context, intent.getStringExtra(EXTRA_SENDER_ID));
Hermes.setDelay(backedOff*2);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,53 @@
package in.raveesh.hermes.receivers;

import android.content.BroadcastReceiver;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.WakefulBroadcastReceiver;
import android.util.Log;

import in.raveesh.hermes.Hermes;

public class HermesBroadcastReceiver extends BroadcastReceiver {
/**
* This {@code HermesBroadcastReceiver} takes care of creating and managing a
* partial wake lock for your app. It passes off the work of processing the GCM
* message to {@code HermesIntentService}.
*
* The wakelock is release when {@code HermesIntentService} calls
* {@code completeWakefulIntent()}
*
*/
public class HermesBroadcastReceiver extends WakefulBroadcastReceiver {

private Class intentService;
public HermesBroadcastReceiver() {
}

protected boolean gotRegistrationId = false;
protected boolean error = false;
protected String registrationID;
/**
* This sets the class which should be called when the GCM message is
* received.
* This method should be called when creating the BroadcastReceiver, preferably in the
* constructor. When {@code onReceive} is called, the @param intentService class would
* be called to process the message
* @param intentService the intent service class which should be called to process the gcm
* message. Should extend {@code HermesIntentService}
*/
public void setIntentService(Class intentService) {
this.intentService = intentService;
Log.d("Hermes", "Setting intentService class to - " + intentService);
}

@Override
public void onReceive(Context context, Intent intent) {
registrationID = intent.getStringExtra("registration_id");
if (registrationID != null) {
gotRegistrationId = true;
if (Hermes.CALLBACK != null) {
Hermes.CALLBACK.registrationComplete(registrationID);
}
} else if (intent.getExtras() != null && intent.getExtras().get("error") != null) {
error = true;
if (Hermes.CALLBACK != null){
Hermes.CALLBACK.registrationFailed(intent.getExtras().get("error").toString());
}
ExponentialBackoffReceiver.attemptRegistration(context, Hermes.getDelay(), Hermes.getSenderId());
} else {
Log.d(Hermes.TAG, "GCM Message Received");

}

if(intentService == null ) throw new UnsupportedOperationException(
"Please use setIntentService in the " +
"constructor of your broadcastReceiver class.");

ComponentName comp = new ComponentName(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to dive into this, but could there be concerns if a package has more than one IntentService here? Wasn't how I was imagining myself to write Hermes, but I can see benefits to this approach.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so there would be a problem with having two intent services, that is why I am using setIntentService, which would be called by the subclass of HermesBroadcastReceiver.

Also, I am doing this because he have to specify the class name which we need to start (and mention it in manifest too.). If I just start HermesIntentService, the user would not receive any update in his subclass intentService (here GcmIntentService), because it wont be registered. So for registering the class properly setIntentService API can be used. This would ensure correct IntentService is called (albeit it has to be registered in manifest too)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, I haven't read through code in detail, could you please add comments?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey I have added some JavaDocs and removed Utils class. Check it out.

context.getPackageName(),
intentService.getName());

startWakefulService(context, (intent.setComponent(comp)));
setResultCode(Activity.RESULT_OK);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package in.raveesh.hermes.receivers;

import android.app.IntentService;
import android.content.Intent;
import android.util.Log;

import com.google.android.gms.gcm.GoogleCloudMessaging;

import in.raveesh.hermes.Hermes;

/**
* Handles the intent which contains the GCM message.
* It holds the wake lock started by {@code HermesBroadcastReceiver} while
* doing the computation and finally releases it by calling
* {@code completeWakefulIntent()}
*
* This class is abstract and methods
* {@code messageReceived}, {@code messageDeleted}, {@code messageSendError},
* {@code messageSendEvent} should be overridden in the subclass.
*
*/
public abstract class HermesIntentService extends IntentService{
protected boolean gotRegistrationId = false;
protected boolean error = false;
protected String registrationID;

public HermesIntentService(String name) {
super(name);
}

@Override
protected void onHandleIntent(Intent intent) {
Log.d("Hermes", "Received intent in HermesIntentService with action - "+intent.getAction());

if(intent.getAction().equals("com.google.android.c2dm.intent.REGISTRATION")) {
registrationID = intent.getStringExtra("registration_id");
if (registrationID != null) {
gotRegistrationId = true;
if (Hermes.CALLBACK != null) {
Hermes.CALLBACK.registrationComplete(registrationID);
}
} else if (intent.getExtras() != null && intent.getExtras().get("error") != null) {
error = true;
if (Hermes.CALLBACK != null){
Hermes.CALLBACK.registrationFailed(intent.getExtras().get("error").toString());
}

// Registration message was received but was error.
ExponentialBackoffReceiver.attemptRegistration(this, Hermes.getDelay(), Hermes.getSenderId());
}
} else if(intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")) {
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
String messageType = gcm.getMessageType(intent);

// Logging and calling the appropriate abstract method.
if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {
Log.d("Hermes", "Message Received");
messageReceived();
} else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {
Log.d("Hermes", "Message Deleted");
messageDeleted();
} else if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {
Log.d("Hermes", "Message Send Error");
messageSendError();
} else if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_EVENT.equals(messageType)) {
Log.d("Hermes", "Message Send Event");
messageSendEvent();
}
}

// Release the wake lock provided by the WakefulBroadcastReceiver.
HermesBroadcastReceiver.completeWakefulIntent(intent);
}

public abstract void messageReceived();
public abstract void messageDeleted();
public abstract void messageSendEvent();
public abstract void messageSendError();
}
12 changes: 7 additions & 5 deletions hermesexample/build.gradle
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 21
buildToolsVersion "21.1.2"
compileSdkVersion 22
buildToolsVersion "22.0.1"

defaultConfig {
applicationId "in.raveesh.hermesexample"
minSdkVersion 9
targetSdkVersion 21
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
Expand All @@ -25,6 +25,8 @@ repositories {

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.0.0'
compile 'in.raveesh:hermes:0.1.0-SNAPSHOT'
compile 'com.android.support:appcompat-v7:22.1.1'
// compile 'in.raveesh:hermes:0.1.0-SNAPSHOT'

compile project (':hermes') // For Development
}
3 changes: 3 additions & 0 deletions hermesexample/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
<category android:name="in.raveesh.hermesexample" />
</intent-filter>
</receiver>

<service android:name=".GcmIntentService"
android:enabled="true"/>
</application>

</manifest>
Loading