/* * The CroudTrip! application aims at revolutionizing the car-ride-sharing market with its easy, * user-friendly and highly automated way of organizing shared Trips. Copyright (C) 2015 Nazeeh Ammari, * Philipp Eichhorn, Ricarda Hohn, Vanessa Lange, Alexander Popp, Frederik Simon, Michael Weber * This program is free software: you can redistribute it and/or modify it under the terms of the GNU * Affero General Public License as published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see http://www.gnu.org/licenses/. */ package org.croudtrip.gcm; 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.os.Bundle; import android.support.v4.app.NotificationCompat; import android.support.v4.content.LocalBroadcastManager; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.android.gms.gcm.GoogleCloudMessaging; import org.croudtrip.Constants; import org.croudtrip.R; import org.croudtrip.activities.MainActivity; import org.croudtrip.api.TripsResource; import org.croudtrip.api.directions.RouteLocation; import org.croudtrip.api.gcm.GcmConstants; import org.croudtrip.api.trips.JoinTripRequest; import org.croudtrip.api.trips.RunningTripQuery; import org.croudtrip.api.trips.SuperTrip; import org.croudtrip.api.trips.TripQuery; import org.croudtrip.fragments.join.JoinDispatchFragment; import org.croudtrip.utils.LifecycleHandler; import javax.inject.Inject; import roboguice.service.RoboIntentService; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action1; import rx.schedulers.Schedulers; import timber.log.Timber; /** * An intent server that handles the gcm messages comming from the server. It will create notifications * so that the user can react on new offered trips or open requests. * Created by Frederik Simon on 08.05.2015. */ public class GcmIntentService extends RoboIntentService { @Inject TripsResource tripsResource; public GcmIntentService() { super("GcmIntentService"); } @Override protected void onHandleIntent(Intent intent) { final Context context = getApplicationContext(); // check for proper GCM message String messageType = GoogleCloudMessaging.getInstance(context).getMessageType(intent); if (!GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) return; // get the type of message and handle it properly String gcmMessageType = intent.getExtras().getString(GcmConstants.GCM_TYPE); Timber.d(gcmMessageType); switch (gcmMessageType) { case GcmConstants.GCM_MSG_REQUEST_EXPIRED: handleJoinRequestExpired(); break; case GcmConstants.GCM_MSG_DUMMY: break; case GcmConstants.GCM_MSG_JOIN_REQUEST: handleJoinRequest(intent); break; case GcmConstants.GCM_MSG_REQUEST_ACCEPTED: handleRequestAccepted(intent); break; case GcmConstants.GCM_MSG_REQUEST_DECLINED: handleRequestDeclined(intent); break; case GcmConstants.GCM_MSG_FOUND_MATCHES: handleFoundMatches(intent); break; case GcmConstants.GCM_MESSAGE_TRIP_CANCELLED_BY_DRIVER: handleTripCanceledByDriver(); break; case GcmConstants.GCM_MESSAGE_TRIP_CANCELLED_BY_PASSENGER: handleTripCanceledByPassenger(); break; case GcmConstants.GCM_MSG_PASSENGER_AT_DESTINATION: handlePassengerAtDestination(); break; case GcmConstants.GCM_MSG_PASSENGER_ENTERED_CAR: handlePassengerEnteredCar(); break; case GcmConstants.GCM_MSG_ARRIVAL_TIME_UPDATE: handleArrivalTimeUpdate(intent); default: break; } GcmBroadcastReceiver.completeWakefulIntent(intent); } private void handleArrivalTimeUpdate(Intent intent) { Timber.d("ARRIVAL_TIME_UPDATE"); // extract join request and offer from message final long joinTripRequestId = Long.parseLong(intent.getExtras().getString(GcmConstants.GCM_MSG_JOIN_REQUEST_ID)); // download the join trip request tripsResource.getJoinRequest(joinTripRequestId) .observeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .subscribe( new Action1<JoinTripRequest>() { @Override public void call(JoinTripRequest joinTripRequest) { final SharedPreferences prefs = getApplicationContext().getSharedPreferences(Constants.SHARED_PREF_FILE_PREFERENCES, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putBoolean(Constants.SHARED_PREF_KEY_ACCEPTED, true); editor.putBoolean(Constants.SHARED_PREF_KEY_WAITING, false); editor.putBoolean(Constants.SHARED_PREF_KEY_SEARCHING, false); editor.putLong(Constants.SHARED_PREF_KEY_TRIP_ID, joinTripRequest.getId()); editor.apply(); Bundle extras = new Bundle(); ObjectMapper mapper = new ObjectMapper(); try { extras.putString(JoinDispatchFragment.KEY_JOIN_TRIP_REQUEST_RESULT, mapper.writeValueAsString(joinTripRequest)); } catch (JsonProcessingException e) { Timber.e("Could not map join trip result"); e.printStackTrace(); } if (LifecycleHandler.isApplicationInForeground()) { Intent startingIntent = new Intent(Constants.EVENT_CHANGE_JOIN_UI); startingIntent.putExtras(extras); LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(startingIntent); } } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { Timber.e("Something went wrong when downloading join request: " + throwable.getMessage()); } }); } private void handlePassengerAtDestination() { if (LifecycleHandler.isApplicationInForeground()) { // send broadcast while the app is running to reload the application Intent startingIntent = new Intent(Constants.EVENT_PASSENGER_REACHED_DESTINATION); LocalBroadcastManager.getInstance(this).sendBroadcast(startingIntent); } } private void handlePassengerEnteredCar() { if (LifecycleHandler.isApplicationInForeground()) { // send broadcast while the app is running to reload the application Intent startingIntent = new Intent(Constants.EVENT_PASSENGER_ENTERED_CAR); LocalBroadcastManager.getInstance(this).sendBroadcast(startingIntent); } } private void handleFoundMatches(Intent intent) { Timber.d("FOUND_MATCHES"); // extract join request and offer from message long queryId = Long.parseLong(intent.getExtras().getString(GcmConstants.GCM_MSG_FOUND_MATCHES_QUERY_ID)); // download the join trip request tripsResource.getQuery(queryId) .observeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .subscribe( new Action1<RunningTripQuery>() { @Override public void call(RunningTripQuery query) { final SharedPreferences prefs = getApplicationContext().getSharedPreferences(Constants.SHARED_PREF_FILE_PREFERENCES, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putBoolean(Constants.SHARED_PREF_KEY_SEARCHING, true); editor.putBoolean(Constants.SHARED_PREF_KEY_ACCEPTED, false); editor.putLong(Constants.SHARED_PREF_KEY_QUERY_ID, -1); editor.apply(); Bundle extras = new Bundle(); extras.putDouble(JoinDispatchFragment.KEY_CURRENT_LOCATION_LATITUDE, query.getQuery().getStartLocation().getLat()); extras.putDouble(JoinDispatchFragment.KEY_CURRENT_LOCATION_LONGITUDE, query.getQuery().getStartLocation().getLng()); extras.putDouble(JoinDispatchFragment.KEY_DESTINATION_LATITUDE, query.getQuery().getDestinationLocation().getLat()); extras.putDouble(JoinDispatchFragment.KEY_DESTINATION_LONGITUDE, query.getQuery().getDestinationLocation().getLng()); extras.putInt(JoinDispatchFragment.KEY_MAX_WAITING_TIME, (int) query.getQuery().getMaxWaitingTimeInSeconds()); if (LifecycleHandler.isApplicationInForeground()) { Intent startingIntent = new Intent(Constants.EVENT_CHANGE_JOIN_UI); startingIntent.putExtras(extras); LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(startingIntent); } else { // create notification for the user Intent startingIntent = new Intent(getApplicationContext(), MainActivity.class); startingIntent.putExtras(extras); PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, startingIntent, PendingIntent.FLAG_UPDATE_CURRENT); createNotification(getString(R.string.found_matches_title), getString(R.string.found_matches_msg), GcmConstants.GCM_NOTIFICATION_FOUND_MATCHES_ID, contentIntent); } } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { Timber.e("Something went wrong when downloading join request: " + throwable.getMessage()); } }); } private void handleRequestDeclined(Intent intent) { Timber.d("REQUEST_DECLINED"); // extract join request and offer from message long joinTripRequestId = Long.parseLong(intent.getExtras().getString(GcmConstants.GCM_MSG_JOIN_REQUEST_ID)); long offerId = Long.parseLong(intent.getExtras().getString(GcmConstants.GCM_MSG_JOIN_REQUEST_OFFER_ID)); // download the join trip request tripsResource.getJoinRequest(joinTripRequestId) .observeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .subscribe( new Action1<JoinTripRequest>() { @Override public void call(JoinTripRequest joinTripRequest) { //Check the two starting positions. If they are the same, this was the declined message from the first driver RouteLocation r1 = joinTripRequest.getSuperTrip().getQuery().getStartLocation(); RouteLocation r2 = joinTripRequest.getSubQuery().getStartLocation(); boolean firstDriver = r1.equals(r2); //save the canceled waiting status only if the first driver canceled if (firstDriver) { final SharedPreferences prefs = getApplicationContext().getSharedPreferences(Constants.SHARED_PREF_FILE_PREFERENCES, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putBoolean(Constants.SHARED_PREF_KEY_SEARCHING, true); editor.putBoolean(Constants.SHARED_PREF_KEY_WAITING, false); editor.apply(); tripsResource.cancelSuperTrip(joinTripRequest.getSuperTrip().getId()) .observeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<SuperTrip>() { @Override public void call(SuperTrip superTrip) { Timber.d("Cancelled your super trip"); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { Timber.e("Could not cancel your trip: " + throwable.getMessage()); } }); } Bundle extras = new Bundle(); TripQuery query = joinTripRequest.getSuperTrip().getQuery(); extras.putDouble(JoinDispatchFragment.KEY_CURRENT_LOCATION_LATITUDE, query.getStartLocation().getLat()); extras.putDouble(JoinDispatchFragment.KEY_CURRENT_LOCATION_LONGITUDE, query.getStartLocation().getLng()); extras.putDouble(JoinDispatchFragment.KEY_DESTINATION_LATITUDE, query.getDestinationLocation().getLat()); extras.putDouble(JoinDispatchFragment.KEY_DESTINATION_LONGITUDE, query.getDestinationLocation().getLng()); extras.putInt(JoinDispatchFragment.KEY_MAX_WAITING_TIME, (int) query.getMaxWaitingTimeInSeconds()); if (LifecycleHandler.isApplicationInForeground()) { //go back to search UI only if the first driver canceled if (firstDriver) { //Toast.makeText(getApplicationContext(), getString(R.string.join_request_declined_msg), Toast.LENGTH_SHORT).show(); Intent startingIntent = new Intent(Constants.EVENT_CHANGE_JOIN_UI); startingIntent.putExtras(extras); LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(startingIntent); } else { Intent startingIntent = new Intent(Constants.EVENT_SECONDARY_DRIVER_DECLINED); startingIntent.putExtras(extras); LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(startingIntent); } } else { // create notification for the user Intent startingIntent = new Intent(getApplicationContext(), MainActivity.class); startingIntent.putExtras(extras); PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, startingIntent, PendingIntent.FLAG_UPDATE_CURRENT); createNotification(getString(R.string.join_request_declined_title), getString(R.string.join_request_declined_msg), GcmConstants.GCM_NOTIFICATION_REQUEST_DECLINED_ID, contentIntent); } } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { Timber.e("Something went wrong when downloading join request: " + throwable.getMessage()); } }); } private void handleRequestAccepted(Intent intent) { Timber.d("REQUEST_ACCEPTED"); // extract join request and offer from message final long joinTripRequestId = Long.parseLong(intent.getExtras().getString(GcmConstants.GCM_MSG_JOIN_REQUEST_ID)); long offerId = Long.parseLong(intent.getExtras().getString(GcmConstants.GCM_MSG_JOIN_REQUEST_OFFER_ID)); // download the join trip request tripsResource.getJoinRequest(joinTripRequestId) .observeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .subscribe( new Action1<JoinTripRequest>() { @Override public void call(JoinTripRequest joinTripRequest) { //Check the two starting positions. If they are the same, this was the declined message from the first driver RouteLocation r1 = joinTripRequest.getSuperTrip().getQuery().getStartLocation(); RouteLocation r2 = joinTripRequest.getSubQuery().getStartLocation(); boolean firstDriver = r1.equals(r2); //save the accepted status only if the first driver accepted if (firstDriver) { final SharedPreferences prefs = getApplicationContext().getSharedPreferences(Constants.SHARED_PREF_FILE_PREFERENCES, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putBoolean(Constants.SHARED_PREF_KEY_ACCEPTED, true); editor.putBoolean(Constants.SHARED_PREF_KEY_WAITING, false); editor.putBoolean(Constants.SHARED_PREF_KEY_SEARCHING, false); editor.putLong(Constants.SHARED_PREF_KEY_TRIP_ID, joinTripRequest.getId()); editor.apply(); } Bundle extras = new Bundle(); ObjectMapper mapper = new ObjectMapper(); try { extras.putString(JoinDispatchFragment.KEY_JOIN_TRIP_REQUEST_RESULT, mapper.writeValueAsString(joinTripRequest)); } catch (JsonProcessingException e) { Timber.e("Could not map join trip result"); e.printStackTrace(); } if (LifecycleHandler.isApplicationInForeground()) { if (firstDriver) { Intent startingIntent = new Intent(Constants.EVENT_CHANGE_JOIN_UI); startingIntent.putExtras(extras); LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(startingIntent); } else { Intent startingIntent = new Intent(Constants.EVENT_SECONDARY_DRIVER_ACCEPTED); startingIntent.putExtras(extras); LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(startingIntent); } } else { // create notification for the user only if the first driver accepts Intent startingIntent = new Intent(getApplicationContext(), MainActivity.class); startingIntent.putExtras(extras); PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, startingIntent, PendingIntent.FLAG_UPDATE_CURRENT); createNotification(getString(R.string.join_request_accepted_title), getString(R.string.join_request_accepted_msg, joinTripRequest.getOffer().getDriver().getFirstName()), GcmConstants.GCM_NOTIFICATION_REQUEST_ACCEPTED_ID, contentIntent); } } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { Timber.e("Something went wrong when downloading join request: " + throwable.getMessage()); } }); } private void handleJoinRequest(Intent intent) { Timber.d("JOIN_REQUEST"); if (LifecycleHandler.isApplicationInForeground()) { // Have screen update itself Intent startingIntent = new Intent(Constants.EVENT_NEW_JOIN_REQUEST); LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(startingIntent); } else { // Show notification // extract join request and offer from message long joinTripRequestId = Long.parseLong(intent.getExtras().getString(GcmConstants.GCM_MSG_JOIN_REQUEST_ID)); // download the join trip request tripsResource.getJoinRequest(joinTripRequestId).observeOn(Schedulers.io()) .subscribeOn(AndroidSchedulers.mainThread()) .subscribe( new Action1<JoinTripRequest>() { @Override public void call(JoinTripRequest joinTripRequest) { // create notification for the user Intent startingIntent = new Intent(getApplicationContext(), MainActivity.class); startingIntent.setAction(MainActivity.ACTION_SHOW_JOIN_TRIP_REQUESTS); PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, startingIntent, PendingIntent.FLAG_UPDATE_CURRENT); createNotification(getString(R.string.join_request_title), getString(R.string.joint_request_msg, joinTripRequest.getSuperTrip().getQuery().getPassenger().getFirstName()), GcmConstants.GCM_NOTIFICATION_JOIN_REQUEST_ID, contentIntent); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { Timber.e("Something went wrong when downloading join request: " + throwable.getMessage()); } }); } } private void handleTripCanceledByDriver() { if (LifecycleHandler.isApplicationInForeground()) { Timber.d("Trip Canceled by Driver"); } else { //Create a notification for the passengers who already joined the trip Intent startingIntent = new Intent(getApplicationContext(), MainActivity.class); startingIntent.setAction(MainActivity.ACTION_SHOW_JOIN_TRIP_FRAGMENT); PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, startingIntent, PendingIntent.FLAG_UPDATE_CURRENT); createNotification(getString(R.string.new_msg), getString(R.string.trip_canceled_msg), GcmConstants.GCM_NOTIFICATION_TRIP_CANCELLED_ID, contentIntent); } //Change the status of Join trip flags final SharedPreferences prefs = getApplicationContext().getSharedPreferences(Constants.SHARED_PREF_FILE_PREFERENCES, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putBoolean(Constants.SHARED_PREF_KEY_SEARCHING, false); editor.putBoolean(Constants.SHARED_PREF_KEY_ACCEPTED, false); editor.putLong(Constants.SHARED_PREF_KEY_QUERY_ID, -1); editor.apply(); } private void handleTripCanceledByPassenger() { Timber.d("Trip Canceled by passenger"); if (LifecycleHandler.isApplicationInForeground()) { // send broadcast while the app is running to reload the application Intent startingIntent = new Intent(Constants.EVENT_PASSENGER_CANCELLED_TRIP); LocalBroadcastManager.getInstance(this).sendBroadcast(startingIntent); } else { // create notification if the application is not in foreground Intent startingIntent = new Intent(getApplicationContext(), MainActivity.class); startingIntent.setAction(MainActivity.ACTION_SHOW_JOIN_TRIP_REQUESTS); PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, startingIntent, PendingIntent.FLAG_UPDATE_CURRENT); createNotification(getString(R.string.new_msg), getString(R.string.trip_canceled_by_passenger), GcmConstants.GCM_NOTIFICATION_TRIP_CANCELLED_ID, contentIntent); } } private void createNotification(String title, String message, int notificationId) { createNotification(title, message, notificationId, null); } private void createNotification(String title, String message, int notificationId, PendingIntent contentIntent) { NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); Notification notification = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_directions_car_white) .setContentTitle(title) .setStyle(new NotificationCompat.BigTextStyle().bigText(message)) .setContentText(message) .setContentIntent(contentIntent) .build(); notification.flags |= Notification.FLAG_AUTO_CANCEL; notificationManager.notify(notificationId, notification); } public void handleJoinRequestExpired() { Timber.d("Request Expired"); if (LifecycleHandler.isApplicationInForeground()) { Timber.d("Request Expired and Broadcast was sent to LocalBroadcastManager"); Intent startingIntent = new Intent(Constants.EVENT_JOIN_REQUEST_EXPIRED); LocalBroadcastManager.getInstance(this).sendBroadcast(startingIntent); } else Timber.d("Request Expired but Broadcast was not sent to LocalBroadcastManager, application not in Foreground"); } }