package com.thebluealliance.androidclient.gcm;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.gson.JsonParseException;
import com.thebluealliance.androidclient.R;
import com.thebluealliance.androidclient.TBAAndroid;
import com.thebluealliance.androidclient.TbaLogger;
import com.thebluealliance.androidclient.accounts.AccountController;
import com.thebluealliance.androidclient.config.AppConfig;
import com.thebluealliance.androidclient.database.Database;
import com.thebluealliance.androidclient.database.DatabaseWriter;
import com.thebluealliance.androidclient.database.tables.FavoritesTable;
import com.thebluealliance.androidclient.database.tables.NotificationsTable;
import com.thebluealliance.androidclient.database.tables.SubscriptionsTable;
import com.thebluealliance.androidclient.datafeed.MyTbaDatafeed;
import com.thebluealliance.androidclient.datafeed.status.TBAStatusController;
import com.thebluealliance.androidclient.di.components.DaggerNotificationComponent;
import com.thebluealliance.androidclient.di.components.NotificationComponent;
import com.thebluealliance.androidclient.eventbus.NotificationsUpdatedEvent;
import com.thebluealliance.androidclient.gcm.notifications.AllianceSelectionNotification;
import com.thebluealliance.androidclient.gcm.notifications.AwardsPostedNotification;
import com.thebluealliance.androidclient.gcm.notifications.BaseNotification;
import com.thebluealliance.androidclient.gcm.notifications.CompLevelStartingNotification;
import com.thebluealliance.androidclient.gcm.notifications.DistrictPointsUpdatedNotification;
import com.thebluealliance.androidclient.gcm.notifications.EventDownNotification;
import com.thebluealliance.androidclient.gcm.notifications.EventMatchVideoNotification;
import com.thebluealliance.androidclient.gcm.notifications.GenericNotification;
import com.thebluealliance.androidclient.gcm.notifications.NotificationTypes;
import com.thebluealliance.androidclient.gcm.notifications.ScheduleUpdatedNotification;
import com.thebluealliance.androidclient.gcm.notifications.ScoreNotification;
import com.thebluealliance.androidclient.gcm.notifications.SummaryNotification;
import com.thebluealliance.androidclient.gcm.notifications.TeamMatchVideoNotification;
import com.thebluealliance.androidclient.gcm.notifications.UpcomingMatchNotification;
import com.thebluealliance.androidclient.helpers.EventTeamHelper;
import com.thebluealliance.androidclient.helpers.MatchHelper;
import com.thebluealliance.androidclient.helpers.MyTBAHelper;
import com.thebluealliance.androidclient.helpers.TeamHelper;
import com.thebluealliance.androidclient.models.StoredNotification;
import com.thebluealliance.androidclient.mytba.MyTbaUpdateService;
import com.thebluealliance.androidclient.renderers.MatchRenderer;
import com.thebluealliance.androidclient.renderers.RendererModule;
import org.greenrobot.eventbus.EventBus;
import android.app.IntentService;
import android.app.Notification;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.content.ContextCompat;
import javax.inject.Inject;
public class GCMMessageHandler extends IntentService implements FollowsChecker {
/**
* Stack (bundle) notifications together into a Group for better UX on Nougat+ and Android Wear
* but not on KitKat because SupportLib 24.2.1 NotificationManagerCompat drops grouped
* notifications (http://stackoverflow.com/a/34953411/1682419) nor on Lollipop API 21 because
* the OS messes up groups (it shows a summary and the first two source notifications as
* separate items instead of one group.)
*/
public static final boolean STACK_NOTIFICATIONS =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1;
/** True if phones/tablets will bundle up the stack using the summary as a header. */
public static final boolean SUMMARY_NOTIFICATION_IS_A_HEADER =
STACK_NOTIFICATIONS && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
/** The setGroup() key to group notifications into a stack/bundle as feasible. */
public static final String GROUP_KEY = STACK_NOTIFICATIONS ? "tba-android" : null;
/** If grouping won't work, use this ID to make each notification replace its predecessor. */
public static final int SINGULAR_NOTIFICATION_ID = 363;
@Inject MyTbaDatafeed mMyTbaDatafeed;
@Inject DatabaseWriter mWriter;
@Inject SharedPreferences mPrefs;
@Inject EventBus mEventBus;
@Inject TBAStatusController mStatusController;
@Inject MatchRenderer mMatchRenderer;
@Inject Database mDb;
@Inject AccountController mAccountController;
@Inject AppConfig mAppConfig;
private NotificationComponent mComponenet;
public GCMMessageHandler() {
this("GCMMessageHandler");
}
public GCMMessageHandler(String name) {
super(name);
}
@Override
public void onCreate() {
super.onCreate();
getComponenet();
mComponenet.inject(this);
}
private void getComponenet() {
if (mComponenet == null) {
TBAAndroid application = ((TBAAndroid) getApplication());
mComponenet = DaggerNotificationComponent.builder()
.applicationComponent(application.getComponent())
.datafeedModule(application.getDatafeedModule())
.rendererModule(new RendererModule())
.build();
}
}
@Override
public boolean followsTeam(Context context, String teamNumber, String matchKey,
String notificationType) {
String currentUser = mAccountController.getSelectedAccount();
String teamKey = TeamHelper.baseTeamKey("frc" + teamNumber); // "frc111"
String teamInterestKey = MyTBAHelper.createKey(currentUser, teamKey); // "r@gmail.com:frc111"
String teamAtEventKey = EventTeamHelper.generateKey(
MatchHelper.getEventKeyFromMatchKey(matchKey), teamKey); // "2016calb_frc111"
String teamAtEventInterestKey = MyTBAHelper.createKey(currentUser, teamAtEventKey);
FavoritesTable favTable = mDb.getFavoritesTable();
SubscriptionsTable subTable = mDb.getSubscriptionsTable();
return favTable.exists(teamInterestKey)
|| favTable.exists(teamAtEventInterestKey)
|| subTable.hasNotificationType(teamInterestKey, notificationType)
|| subTable.hasNotificationType(teamAtEventInterestKey, notificationType);
}
@Override
protected void onHandleIntent(Intent intent) {
Bundle extras = intent.getExtras();
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
String messageType = gcm.getMessageType(intent);
TbaLogger.d("GCM Message type: " + messageType);
TbaLogger.d("Intent extras: " + extras.toString());
// We got a standard message. Parse it and handle it.
String type = extras.getString("message_type", "");
String data = extras.getString("message_data", "");
handleMessage(getApplicationContext(), type, data);
TbaLogger.i("Received : (" + type + ") " + data);
GCMBroadcastReceiver.completeWakefulIntent(intent);
}
public void handleMessage(Context c, String messageType, String messageData) {
try {
BaseNotification notification = null;
switch (messageType) {
case NotificationTypes.UPDATE_FAVORITES:
Intent favIntent = MyTbaUpdateService.newInstance(c, true, false);
c.startService(favIntent);
break;
case NotificationTypes.UPDATE_SUBSCRIPTIONS:
Intent subIntent = MyTbaUpdateService.newInstance(c, false, true);
c.startService(subIntent);
break;
case NotificationTypes.PING:
case NotificationTypes.BROADCAST:
notification = new GenericNotification(messageType, messageData);
break;
case NotificationTypes.MATCH_SCORE:
case "score":
notification = new ScoreNotification(messageData, mWriter.getMatchWriter().get(), mMatchRenderer);
break;
case NotificationTypes.UPCOMING_MATCH:
notification = new UpcomingMatchNotification(messageData);
break;
case NotificationTypes.ALLIANCE_SELECTION:
notification = new AllianceSelectionNotification(messageData, mWriter.getEventWriter().get());
break;
case NotificationTypes.LEVEL_STARTING:
notification = new CompLevelStartingNotification(messageData);
break;
case NotificationTypes.SCHEDULE_UPDATED:
notification = new ScheduleUpdatedNotification(messageData);
break;
case NotificationTypes.AWARDS:
notification = new AwardsPostedNotification(messageData, mWriter.getAwardListWriter().get());
break;
case NotificationTypes.DISTRICT_POINTS_UPDATED:
notification = new DistrictPointsUpdatedNotification(messageData);
break;
case NotificationTypes.TEAM_MATCH_VIDEO:
notification = new TeamMatchVideoNotification(messageData, mWriter.getMatchWriter().get());
break;
case NotificationTypes.EVENT_MATCH_VIDEO:
notification = new EventMatchVideoNotification(messageData);
break;
case NotificationTypes.EVENT_DOWN:
notification = new EventDownNotification(messageData);
/* Don't break, we also want to schedule a status update here */
case NotificationTypes.SYNC_STATUS:
TbaLogger.i("Updating TBA API Status via push notification");
mStatusController.scheduleStatusUpdate(c);
break;
}
if (notification == null) return;
try {
notification.parseMessageData();
} catch (JsonParseException e) {
TbaLogger.e("Error parsing incoming message json");
e.printStackTrace();
return;
}
boolean enabled = mPrefs.getBoolean("enable_notifications", true);
if (enabled) {
Notification built;
built = notification.buildNotification(c, this);
if (built == null) return;
/* Update the data coming from this notification in the local db */
notification.updateDataLocally();
/* Store this notification for future access */
StoredNotification stored = notification.getStoredNotification();
if (stored != null) {
NotificationsTable table = mDb.getNotificationsTable();
table.add(stored);
table.prune();
}
// Tell interested parties that a new notification has arrived
mEventBus.post(new NotificationsUpdatedEvent(notification));
if (notification.shouldShow()) {
if (SummaryNotification.isNotificationActive(c, mDb)) {
// Multiple notifications: Stack them into a Group by posting the new
// notification THEN (re)posting a summary. If we can't stack them, post
// the new one XOR a summary, all with the same ID to replace any
// predecessor notification.
if (STACK_NOTIFICATIONS) {
notify(c, notification, built);
}
notification = new SummaryNotification(mDb);
built = notification.buildNotification(c, this);
}
notify(c, notification, built);
}
}
} catch (Exception e) {
// We probably tried to post a null notification or something like that. Oops...
e.printStackTrace();
}
}
private void notify(Context c, BaseNotification notification, Notification built) {
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(c);
int id = STACK_NOTIFICATIONS ? notification.getNotificationId() : SINGULAR_NOTIFICATION_ID;
setNotificationParams(built, c, notification.getNotificationType(), mPrefs);
notificationManager.notify(id, built);
}
private static Uri getSoundUri(Context c, int soundId) {
return Uri.parse("android.resource://" + c.getPackageName() + "/" + soundId);
}
private static void setNotificationParams(Notification built, Context c, String messageType, SharedPreferences prefs) {
/* Set notification parameters */
if (prefs.getBoolean("notification_vibrate", true)) {
// Delay vibration to match the system audio delay. Pulse with the beat.
built.vibrate = new long[]{200, 70, 90, 70, 90, 80};
}
if (prefs.getBoolean("notification_tone", true)) {
built.sound = getSoundUri(c, R.raw.something_you_dont_mess_with);
}
if (prefs.getBoolean("notification_led_enabled", true)) {
built.ledARGB = prefs.getInt("notification_led_color",
ContextCompat.getColor(c, R.color.primary));
built.ledOnMS = 1000;
built.ledOffMS = 1000;
built.flags |= Notification.FLAG_SHOW_LIGHTS;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
int priority = Notification.PRIORITY_HIGH;
switch (messageType) {
case NotificationTypes.PING:
priority = Notification.PRIORITY_LOW;
break;
case NotificationTypes.SUMMARY:
// If Android will really display a component notification then a group summary,
// don't let the summary heads-up atop the component.
if (SUMMARY_NOTIFICATION_IS_A_HEADER) {
priority = Notification.PRIORITY_DEFAULT;
}
break;
}
boolean headsUpPref = prefs.getBoolean("notification_headsup", true);
if (headsUpPref) {
built.priority = priority;
} else {
built.priority = Notification.PRIORITY_DEFAULT;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
built.visibility = Notification.VISIBILITY_PUBLIC;
built.category = Notification.CATEGORY_SOCIAL;
}
}
}
}