/************************************************************************************************** * Copyright (C) 2010 Sense Observation Systems, Rotterdam, the Netherlands. All rights reserved. * *************************************************************************************************/ package nl.sense_os.service.push; import java.io.IOException; import nl.sense_os.service.R; import nl.sense_os.service.ServiceStateHelper; import nl.sense_os.service.commonsense.SenseApi; import nl.sense_os.service.configuration.ConfigurationService; import nl.sense_os.service.configuration.RequirementReceiver; import nl.sense_os.service.constants.SensePrefs; import nl.sense_os.service.constants.SensePrefs.Auth; import nl.sense_os.service.constants.SensePrefs.Status; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Bundle; import android.support.v4.app.NotificationCompat; import android.util.Log; import android.widget.Toast; import com.google.android.gcm.GCMBaseIntentService; import com.google.android.gcm.GCMRegistrar; /** * Class that handles new message received with Google Cloud Messaging.<br/> * <br/> * It handles the registration of the gcm_id (identifier sent by Google for the device), and * synchronizes that identifier to CommonSense. This class also handles commands sent by CommonSense * to this device. * * @author Ahmy Yulrizka <ahmy@sense-os.nl> */ public class GCMReceiver extends GCMBaseIntentService { private final String TAG = "push"; public static final String SENDER_ID = "926669645310"; public static final String KEY_GCM_ID = "gcm_id"; public static final String TYPE_TOAST = "toast"; public static final String TYPE_NOTIFICATION = "notification"; public static final String TYPE_SERVICE = "service"; public static final String TYPE_UPDATE_CONFIGURATION = "update_configuration"; public static final String TYPE_USE_CONFIGURATION = "use_configuration"; private static final int NOTIF_ID = 0x01abc; public final static String ACTION_GOT_CONFIGURATION = "nl.sense_os.service.push.got_requirement"; public GCMReceiver() { super(SENDER_ID); } /** * Broadcast new requirements * * @param context * @param req * @see ConfigurationService#onHandleIntent */ private void broadcastRequirement(Context context, String req) { Intent i = new Intent(ACTION_GOT_CONFIGURATION); i.putExtra(RequirementReceiver.EXTRA_REQUIREMENTS, req); sendBroadcast(i); } /** * Called when an error occurs with GCM * * @param context * @param errorId * @see http://developer.android.com/google/gcm/gcm.html */ @Override protected void onError(Context context, String errorId) { // TODO Handle if out of sync, and other type errors ? } /** * Handles new message when they are received from GCM. It first verifies that the message is * for the intended user, parse the type of the message and run the command accordingly.<br/> * <br/> * The intent can contain the the following data: * <ul> * <li>username: the logged in username</li> * <li>type: the of the message. possible value ("toast", "notification", "service", * "update_configuration", "use_configuration")</li> * <li>content: the content of the message according to the type</li> * <li>toast: the message of the toast</li> * <li>notification: {"title": "title of notification", SenseApi.KEY_CONTENT: "content of notification"}</li> * <li>service: "1" for start and "0" for stop</li> * <li>update_configuration: null</li> * <li>use_configuration: "configuration_id" from commonSense</li> * </ul> * * @param context * @param intent */ @Override protected void onMessage(Context context, Intent intent) { Bundle extras = intent.getExtras(); String recipient = extras.getString("username"); SharedPreferences authPrefs = getSharedPreferences(SensePrefs.AUTH_PREFS, MODE_PRIVATE); String username = authPrefs.getString(Auth.LOGIN_USERNAME, null); // check if I am the intended recipient or else ignore the message if (recipient.equals(username)) { String type = extras.getString("type"); String content = extras.getString(SenseApi.RESPONSE_CONTENT); // switch using if (type.equals(TYPE_TOAST)) { Toast.makeText(this, content, Toast.LENGTH_LONG).show(); } else if (type.equals(TYPE_NOTIFICATION)) { this.showNotification(context, content); } else if (type.equals(TYPE_SERVICE)) { this.toggleService(context, content); } else if (type.equals(TYPE_UPDATE_CONFIGURATION)) { this.updateConfiguration(context); } else if (type.equals(TYPE_USE_CONFIGURATION)) { this.useConfiguration(context, content); } } } /** * Called when the application receive the GCM id from Google. This function will sync the * registration ID to the CommonSense. * * @param context * @param registrationId */ @Override protected void onRegistered(Context context, String registrationId) { Log.v(TAG, "RegisterGCMIntentService called with registration: " + registrationId); try { Log.v(TAG, "Registering gcm id to commonSense server"); SenseApi.registerGCMId(this, registrationId); } catch (IOException e) { Log.e(TAG, "Sending registration id failed"); e.printStackTrace(); } catch (JSONException e) { Log.e(TAG, "Error parsing JSON"); e.printStackTrace(); } catch (IllegalStateException e) { Log.e(TAG, "Invalid response from common sense"); e.printStackTrace(); } } /** * Called when the app receives unregister message from Google. * * @param context * @param registrationId */ @Override protected void onUnregistered(Context context, String registrationId) { Log.i(TAG, "Device unregistered"); if (GCMRegistrar.isRegisteredOnServer(context)) { // ServerUtilities.unregister(context, registrationId); } else { // This callback results from the call to unregister made on // ServerUtilities when the registration to the server failed. Log.i(TAG, "Ignoring unregister callback"); } } /** * Shows a notification to user. * * @param context * application context * @param jsonContent * String representation of JSon object consist of {title, text} */ private void showNotification(Context context, String jsonContent) { JSONObject content_json = null; String title = ""; String text = ""; try { content_json = new JSONObject(jsonContent); title = content_json.getString("title"); text = content_json.getString(SenseApi.RESPONSE_CONTENT); NotificationCompat.Builder builder = new NotificationCompat.Builder(this); builder.setDefaults(Notification.DEFAULT_SOUND) .setSmallIcon(R.drawable.ic_stat_sense) .setWhen(System.currentTimeMillis()).setContentTitle(title) .setContentText(text).setTicker(text) .setContentIntent(PendingIntent.getService(this, 0, new Intent(), 0)); NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); nm.notify(NOTIF_ID, builder.build()); } catch (JSONException e) { Log.e(TAG, "Error parsing notification json", e); } } /** * Starts or stops the Sense service * * @param context * @param toggle */ private void toggleService(Context context, String toggle) { boolean started = ServiceStateHelper.getInstance(context).isStarted(); if (toggle.equals("1") && !started) { Editor editor = getSharedPreferences(SensePrefs.STATUS_PREFS, MODE_PRIVATE).edit(); editor.putBoolean(Status.MAIN, true); editor.commit(); Intent task = new Intent(context.getString(R.string.action_sense_service)); context.startService(task); } else if (toggle.equals("0") && started) { Editor editor = getSharedPreferences(SensePrefs.STATUS_PREFS, MODE_PRIVATE).edit(); editor.putBoolean(Status.MAIN, false); editor.commit(); Intent task = new Intent(context.getString(R.string.action_sense_service)); context.startService(task); } } /** * Calls CommonSense API to get the new configuration * * @param context */ private void updateConfiguration(Context context) { try { String requirements = SenseApi.getDeviceConfiguration(this); broadcastRequirement(context, requirements); } catch (IOException e) { e.printStackTrace(); } catch (JSONException e) { e.printStackTrace(); } } /** * Calls CommonSense to get the specific configuration for a given configuration ID. * * @param context * @param configurationId * Configuration ID from CommonSense */ private void useConfiguration(Context context, String configurationId) { try { String configuration = SenseApi.getDeviceConfiguration(this, configurationId); JSONObject configObj = new JSONObject(configuration); JSONArray requirementsArr = configObj.getJSONArray("requirements"); JSONObject requirements = new JSONObject(); if (requirementsArr.length() > 0) { JSONObject value = new JSONObject(requirementsArr.getJSONObject(0).getString( "value")); requirements.put("requirements", value); } String requrimentStr = requirements.toString(); Log.v(TAG, requrimentStr); broadcastRequirement(context, requrimentStr); } catch (IOException e) { e.printStackTrace(); } catch (JSONException e) { e.printStackTrace(); } } }