com.numenta.taurus.service.TaurusNotificationService.java Source code

Java tutorial

Introduction

Here is the source code for com.numenta.taurus.service.TaurusNotificationService.java

Source

/*
 * Numenta Platform for Intelligent Computing (NuPIC)
 * Copyright (C) 2015, Numenta, Inc.  Unless you have purchased from
 * Numenta, Inc. a separate commercial license for this software code, the
 * following terms and conditions apply:
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero Public License version 3 as
 * published by the Free Software Foundation.
 *
 * 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 Public License for more details.
 *
 * You should have received a copy of the GNU Affero Public License
 * along with this program.  If not, see http://www.gnu.org/licenses.
 *
 * http://numenta.org/licenses/
 *
 */

package com.numenta.taurus.service;

import com.numenta.core.service.HTMException;
import com.numenta.core.service.DataService;
import com.numenta.core.service.NotificationService;
import com.numenta.core.utils.DataUtils;
import com.numenta.core.utils.NotificationUtils;
import com.numenta.core.utils.Pair;
import com.numenta.taurus.R;
import com.numenta.taurus.TaurusApplication;
import com.numenta.taurus.data.AnomalyValue;
import com.numenta.taurus.data.Notification;
import com.numenta.taurus.data.TaurusDataFactory;
import com.numenta.taurus.data.TaurusDatabase;
import com.numenta.taurus.metric.MetricType;

import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.text.format.DateUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.numenta.taurus.preference.TaurusPreferenceConstants.PREF_NOTIFICATIONS_ENABLE;
import static com.numenta.taurus.preference.TaurusPreferenceConstants.PREF_NOTIFICATIONS_FREQUENCY;
import static com.numenta.taurus.preference.TaurusPreferenceConstants.PREF_NOTIFICATIONS_RINGTONE;
import static com.numenta.taurus.preference.TaurusPreferenceConstants.PREF_NOTIFICATIONS_VIBRATE;
import static com.numenta.taurus.preference.TaurusPreferenceConstants.PREF_NOTIFICATION_LAST_RUN_TIME;

/**
 * Taurus notification service
 *
 * <p>
 * Taurus will only notify on stock anomalies for user's "Favorite" companies.
 * The notification frequency is based on the following rules:
 * <ol>
 *
 * <li><b>No Limit:</b> Notify every time any of the favorite companies has an anomaly</li>
 *
 * <li><b>1 per <i>X</i> Hour(s):</b> Notify only once on the first anomaly received within
 * <i>X</i> hours for any of the favorite companies. Once the first anomaly notification is
 * fired for the company, all other anomalies for that company will be ignored for a period
 * of <i>X</i> hours. Currently <i>'X'</i> period can be <i>1,2,8 or 24 hours</i></li>
 *
 * </ol>
 * </p>
 */
public class TaurusNotificationService extends NotificationService {

    public TaurusNotificationService(DataService service) {
        super(service);
    }

    /**
     * Check if we need to fire new notifications based on the current application data and state
     */
    protected void synchronizeNotifications() throws HTMException, IOException {
        Context context = getService().getApplicationContext();
        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);

        // Check if the notifications are enabled
        boolean enable = prefs.getBoolean(PREF_NOTIFICATIONS_ENABLE, true);
        if (enable) {
            final long now = System.currentTimeMillis();

            // Get last time we checked for notifications
            final long lastRunTime = DataUtils
                    .floorTo60minutes(prefs.getLong(PREF_NOTIFICATION_LAST_RUN_TIME, now));
            prefs.edit().putLong(PREF_NOTIFICATION_LAST_RUN_TIME, now).apply();

            // Get user frequency preference
            final long frequency = Long.parseLong(prefs.getString(PREF_NOTIFICATIONS_FREQUENCY, "0")) * 1000;

            // Check for pending notifications since last time
            List<Notification> notifications = getPendingNotifications(lastRunTime, now, frequency);
            if (notifications.isEmpty()) {
                // No new notifications
                return;
            }

            // Build notification based on user settings
            NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context)
                    .setOnlyAlertOnce(true).setSmallIcon(com.numenta.core.R.drawable.ic_launcher)
                    .setAutoCancel(true);

            // Get notifications ringtone
            String ringUrlStr = prefs.getString(PREF_NOTIFICATIONS_RINGTONE, null);
            if (ringUrlStr != null && !ringUrlStr.trim().isEmpty()) {
                notificationBuilder.setSound(Uri.parse(ringUrlStr));
            }

            // Check if we should vibrate
            if (prefs.getBoolean(PREF_NOTIFICATIONS_VIBRATE, false)) {
                notificationBuilder.setDefaults(NotificationCompat.DEFAULT_VIBRATE);
                notificationBuilder.setVibrate(new long[] { 1000l });
            }

            // Fire new notifications
            NotificationUtils.createGroupedOsNotification(notificationBuilder, notifications, false);

            // Update notification times
            for (Notification notification : notifications) {
                TaurusApplication.setLastNotificationTime(notification.getInstanceId(),
                        notification.getTimestamp());
            }
        }
        fireNotificationChangedEvent(getService());
    }

    /**
     * Generate a list of Pending Notifications for the given period and frequency
     *
     * @param from      The initial timestamp of the period to check (inclusive)
     * @param to        The end timestamp of the period to check (inclusive)
     * @param frequency The frequency in which to fire notifications in milliseconds
     * @return pending notifications matching the criteria
     */
    List<Notification> getPendingNotifications(long from, long to, long frequency) {
        TaurusDatabase database = TaurusApplication.getDatabase();
        if (database == null) {
            return Collections.emptyList();
        }
        ArrayList<Notification> results = new ArrayList<Notification>();
        HashMap<String, Pair<Long, AnomalyValue>> anomalies = new HashMap<String, Pair<Long, AnomalyValue>>();
        EnumSet<MetricType> mask;
        // Get all anomalies for favorite instances
        for (String instance : TaurusApplication.getFavoriteInstances()) {
            // Check last time a notification was fired for this instance
            long lastFired = TaurusApplication.getLastNotificationTime(instance);
            if (lastFired > to - frequency) {
                // This instance already fired a notification for this period
                continue;
            }

            // Check for anomalies
            List<Pair<Long, AnomalyValue>> data = database.getInstanceData(instance, from, to);
            for (Pair<Long, AnomalyValue> value : data) {
                if (value.second == null) {
                    continue;
                }
                // Check for "red" anomalies
                float logScale = (float) DataUtils.logScale(Math.abs(value.second.anomaly));
                if (logScale >= TaurusApplication.getRedBarFloor()) {
                    // Check if found new stock related anomaly
                    mask = MetricType.fromMask(value.second.metricMask);
                    if (!anomalies.containsKey(instance) && !Collections.disjoint(mask, MetricType.STOCK_TYPES)) {
                        anomalies.put(instance, value);
                    }
                }
            }
        }
        // Create notifications based on anomalies filtered for the period
        TaurusDataFactory factory = database.getDataFactory();
        long timestamp;
        String instance;
        String text;
        AnomalyValue value;
        for (Map.Entry<String, Pair<Long, AnomalyValue>> entry : anomalies.entrySet()) {
            timestamp = entry.getValue().first;
            value = entry.getValue().second;
            mask = MetricType.fromMask(value.metricMask);
            instance = entry.getKey();
            text = formatAnomalyTitle(instance, mask, timestamp);
            results.add(factory.createNotification(instance, timestamp, text));
        }
        return results;
    }

    /**
     * Format anomaly title
     *
     * @param company   The company name
     * @param types     The anomaly types triggering the anomaly
     * @param timestamp The time the anomaly was fired
     * @return The formatted text suitable for OS notification
     */
    String formatAnomalyTitle(String company, EnumSet<MetricType> types, long timestamp) {
        Context ctx = getService();
        String anomalyTypes = "";

        if (!Collections.disjoint(types, MetricType.STOCK_TYPES)) {
            if (types.contains(MetricType.TwitterVolume)) {
                anomalyTypes = ctx.getString(R.string.header_stock_twitter);
            } else {
                anomalyTypes = ctx.getString(R.string.header_stock);
            }
        } else if (types.contains(MetricType.TwitterVolume)) {
            anomalyTypes = ctx.getString(R.string.header_twitter);
        }
        return String.format(ctx.getString(R.string.taurus_notification_description_template), company,
                DateUtils.formatDateTime(ctx, timestamp, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME),
                anomalyTypes);
    }
}