org.zoumbox.mh_dla_notifier.Receiver.java Source code

Java tutorial

Introduction

Here is the source code for org.zoumbox.mh_dla_notifier.Receiver.java

Source

/*
 * #%L
 * MountyHall DLA Notifier
 * $Id$
 * $HeadURL$
 * %%
 * Copyright (C) 2012 - 2014 Zoumbox.org
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */
package org.zoumbox.mh_dla_notifier;

import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Set;

import org.zoumbox.mh_dla_notifier.profile.MissingLoginPasswordException;
import org.zoumbox.mh_dla_notifier.profile.ProfileProxy;
import org.zoumbox.mh_dla_notifier.profile.v2.ProfileProxyV2;
import org.zoumbox.mh_dla_notifier.troll.Troll;
import org.zoumbox.mh_dla_notifier.troll.Trolls;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.AudioManager;
import android.media.RingtoneManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.StrictMode;
import android.os.SystemClock;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.RemoteViews;

/**
 * Classe appele lorsqu'un rveil de l'application survient (approche d'une DLA ou vrification intermdiaire)
 *
 * @author Arno <arno@zoumbox.org>
 */
public class Receiver extends BroadcastReceiver {

    private static final String TAG = MhDlaNotifierConstants.LOG_PREFIX + Receiver.class.getSimpleName();

    public static final long[] VIBRATE_PATTERN = new long[] { 100, 250, 150, 250, 150, 700 };
    public static final long[] NO_VIBRATION = new long[] { 100 };

    private ProfileProxy profileProxy;

    public ProfileProxy getProfileProxy() {
        if (profileProxy == null) {
            profileProxy = new ProfileProxyV2();
        }
        return profileProxy;
    }

    protected boolean needsNotificationAccordingToPA(PreferencesHolder preferences, int pa) {
        boolean result = pa > 0 || preferences.notifyWithoutPA;
        return result;
    }

    protected Predicate<Context> justRestarted() {
        Predicate<Context> result = new Predicate<Context>() {
            @Override
            public boolean apply(Context context) {
                // Check if the device just restarted
                long elapsedSinceLastRestartCheck = getProfileProxy().getElapsedSinceLastRestartCheck(context);
                Log.d(TAG, "Elapsed since last restart check: " + elapsedSinceLastRestartCheck + "ms ~= "
                        + (elapsedSinceLastRestartCheck / 60000) + "min");

                long uptime = SystemClock.uptimeMillis();
                Log.d(TAG, "Uptime: " + uptime + "ms ~= " + (uptime / 60000) + "min");

                boolean result = elapsedSinceLastRestartCheck > uptime;
                if (result) {
                    getProfileProxy().restartCheckDone(context);
                }
                Log.d(TAG, "Device restarted since last check: " + result);
                return result;
            }
        };
        return result;
    }

    protected Predicate<Context> shouldUpdateBecauseOfRestart(final String trollId) {
        Predicate<Context> predicate = new Predicate<Context>() {
            @Override
            public boolean apply(Context context) {
                boolean result = false;
                // Check if the device restarted since last update
                Long elapsedSinceLastSuccess = getProfileProxy().getElapsedSinceLastUpdateSuccess(context, trollId);
                if (elapsedSinceLastSuccess != null) {
                    Log.d(TAG, "Elapsed since last update success: " + elapsedSinceLastSuccess + "ms ~= "
                            + (elapsedSinceLastSuccess / 60000) + "min");

                    long uptime = SystemClock.uptimeMillis();
                    Log.d(TAG, "Uptime: " + uptime + "ms ~= " + (uptime / 60000) + "min");

                    result = elapsedSinceLastSuccess > uptime; // Device restarted since last update
                    Log.d(TAG, "shouldUpdateBecauseOfRestart: " + result);
                    result &= elapsedSinceLastSuccess > (1000l * 60l * 60l * 2l); // 2 hours
                    Log.d(TAG, "shouldUpdateBecauseOfRestart (<=2hours): " + result);
                }
                return result;
            }
        };
        return predicate;
    }

    protected Predicate<Context> shouldUpdateBecauseOfNetworkFailure(final String trollId) {
        Predicate<Context> predicate = new Predicate<Context>() {
            @Override
            public boolean apply(Context context) {
                String lastUpdateResult = getProfileProxy().getLastUpdateResult(context, trollId);
                Log.d(TAG, "lastUpdateResult: " + lastUpdateResult);
                boolean result = lastUpdateResult != null && lastUpdateResult.startsWith("NETWORK ERROR");
                Log.d(TAG, "shouldUpdateBecauseOfNetworkFailure: " + result);
                return result;
            }
        };
        return predicate;
    }

    @Override
    public void onReceive(Context context, Intent intent) {

        String intentAction = intent.getAction();
        Log.d(TAG, String.format("<<< %s#onReceive action=%s", getClass().getName(), intentAction));

        boolean connectivityChanged = ConnectivityManager.CONNECTIVITY_ACTION.equals(intentAction);
        boolean justGotConnection = false;
        if (connectivityChanged) {

            ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

            NetworkInfo activeNetwork = cm.getActiveNetworkInfo();

            justGotConnection = activeNetwork != null && activeNetwork.isConnected();
            Log.d(TAG, "Connectivity change. isConnected=" + justGotConnection);
        }

        if (connectivityChanged && !justGotConnection) {
            Log.d(TAG, "Just lost connectivity, nothing to do");
            return;
        }

        String trollId = intent.getStringExtra(Alarms.EXTRA_TROLL_ID);
        if (Strings.isNullOrEmpty(trollId)) {
            Set<String> trollIds = getProfileProxy().getTrollIds(context);
            if (trollIds.isEmpty()) {
                Log.d(TAG, "No troll registered, exiting...");
                return;
            }
            trollId = trollIds.iterator().next();
            Log.d(TAG, "TrollId not defined, using the fist one: " + trollId);
        }

        if (!getProfileProxy().isPasswordDefined(context, trollId)) {
            Log.d(TAG, "Troll password is not defined, exiting...");
            return;
        }

        // If type is provided, request for an update
        String type = intent.getStringExtra(Alarms.EXTRA_TYPE);
        boolean requestUpdate = !Strings.isNullOrEmpty(type);

        // If device just started, request for wakeups registration
        boolean requestAlarmRegistering = justRestarted().apply(context);

        // Just go the internet connection back. Update will be necessary if
        //  - device restarted since last update and last update in more than 2 hours ago
        //  - last update failed because of network error
        if (!requestUpdate && justGotConnection) { // !requestUpdate because no need to check if update is already requested
            requestUpdate = Predicates
                    .or(shouldUpdateBecauseOfRestart(trollId), shouldUpdateBecauseOfNetworkFailure(trollId))
                    .apply(context);
        }

        Log.d(TAG, String.format("requestUpdate=%b ; requestAlarmRegistering=%b", requestUpdate,
                requestAlarmRegistering));

        // FIXME AThimel 14/02/14 Remove ASAP
        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);

        try {
            Troll troll = getProfileProxy().fetchTrollWithoutUpdate(context, trollId).left();
            if (requestUpdate) {
                // If the current DLA already has no more PA, skip update
                boolean skipUpdate = false;
                if (AlarmType.CURRENT_DLA.name().equals(type)) {
                    skipUpdate = troll.getPa() == 0 && MhDlaNotifierUtils.IS_IN_THE_FUTURE.apply(troll.getDla());
                }
                if (skipUpdate) {
                    trollLoaded(troll, context, false);
                } else {
                    refreshDla(context, trollId);
                }
            } else if (requestAlarmRegistering) {
                trollLoaded(troll, context, false);
            } else {
                Log.d(TAG, "Skip loading Troll");
            }
        } catch (MissingLoginPasswordException mde) {
            Log.w(TAG, "Missing trollId and/or password, exiting...");
        }
    }

    protected void trollLoaded(Troll troll, Context receivedContext, boolean requestUpdate) {

        if (troll != null) {
            PreferencesHolder preferences = PreferencesHolder.load(receivedContext);

            // Re-schedule them (in case it changed)
            String trollId = troll.getNumero();
            if (!Strings.isNullOrEmpty(trollId)) {
                try {
                    Map<AlarmType, Date> alarms = Alarms.getAlarms(receivedContext, getProfileProxy(), trollId);

                    Date now = new Date();

                    Date beforeCurrentDla = alarms.get(AlarmType.CURRENT_DLA);
                    Date currentDla = troll.getDla();
                    Date beforeNextDla = alarms.get(AlarmType.NEXT_DLA);
                    Date nextDla = Trolls.GET_NEXT_DLA.apply(troll);
                    Date nextDlaActivation = alarms.get(AlarmType.NEXT_DLA_ACTIVATION);

                    if (between(now, beforeCurrentDla, currentDla)) {
                        Log.d(TAG, String.format("Need to notify DLA='%s' about to expire", currentDla));
                        int pa = troll.getPa();
                        boolean willNotify = needsNotificationAccordingToPA(preferences, pa);
                        Log.d(TAG, String.format("PA=%d. Will notify? %b", pa, willNotify));
                        if (willNotify) {
                            notifyCurrentDlaAboutToExpire(receivedContext, currentDla, pa, preferences);
                        }
                    } else {
                        Log.d(TAG, String.format("No need to notify for DLA=%s", currentDla));
                    }

                    if (between(now, nextDlaActivation, beforeNextDla)) {
                        Log.d(TAG, String.format("Need to notify NDLA='%s' not activated", nextDla));
                        notifyNextDlaNotActivated(receivedContext, nextDla, preferences);
                    } else {
                        Log.d(TAG, String.format("No need to notify for NDLA=%s", nextDla));
                    }

                    if (between(now, beforeNextDla, nextDla)) {
                        Log.d(TAG, String.format("Need to notify NDLA='%s' about to expire", nextDla));
                        notifyNextDlaAboutToExpire(receivedContext, nextDla, preferences);
                    } else {
                        Log.d(TAG, String.format("No need to notify for NDLA=%s", nextDla));
                    }

                    if (requestUpdate && troll.getPvVariation() < 0 && preferences.notifyOnPvLoss) {
                        int pvLoss = Math.abs(troll.getPvVariation());
                        Log.d(TAG, String.format("Troll lost %d PV", pvLoss));
                        notifyPvLoss(receivedContext, pvLoss, troll.getPv(), preferences);
                    }

                    checkForWidgetsUpdate(receivedContext, troll);
                } catch (MissingLoginPasswordException e) {
                    e.printStackTrace();
                }

                // Re-schedule them (in case it changed due to update)
                try {
                    Alarms.scheduleAlarms(receivedContext, getProfileProxy(), trollId);
                } catch (MissingLoginPasswordException e) {
                    Log.w(TAG, "Missing trollId and/or password, unable to schedule alarms...");
                }
            }
        }

    }

    protected boolean between(Date date, Date lowerBound, Date upperBound) {
        if (date == null) {
            return false;
        }
        try {
            return date.compareTo(lowerBound) >= 0 && date.compareTo(upperBound) <= 0;
        } catch (Exception eee) {
            Log.e(TAG, "Unable to compare dates. Date=" + date + " ; lowerBound=" + lowerBound + " ; upperBound="
                    + upperBound, eee);
            return false;
        }
    }

    protected void refreshDla(Context context, String trollId) {
        Troll troll = null;
        try {
            troll = getProfileProxy().refreshDLA(context, trollId);
        } catch (MissingLoginPasswordException e) {
            Log.w(TAG, "Missing trollId and/or password, unable to refresh DLA...");
        }
        trollLoaded(troll, context, true);
    }

    //    protected void refreshDla(Context context, String trollId, boolean requestUpdate) {
    //        new RefreshDlaTask(context, requestUpdate).doInBackground(trollId);
    //    }
    //
    //    private class RefreshDlaTask extends AsyncTask<String, Void, Troll> {
    //
    //        protected Context context;
    //        protected boolean requestUpdate;
    //
    //        private RefreshDlaTask(Context context, boolean requestUpdate) {
    //            this.context = context;
    //            this.requestUpdate = requestUpdate;
    //        }
    //
    //        @Override
    //        protected void onPreExecute() {
    //            // nothing to do
    //        }
    //
    //        @Override
    //        protected Troll doInBackground(String ... params) {
    //            Troll troll = null;
    //            try {
    //                String trollId = params[0];
    //
    //                troll = getProfileProxy().refreshDLA(context, trollId);
    //            } catch (MhDlaException e) {
    //                e.printStackTrace();
    //            }
    //            return troll;
    //        }
    //
    //        @Override
    //        protected void onPostExecute(Troll result) {
    //            trollLoaded(result, context, requestUpdate);
    //        }
    //    }

    protected void checkForWidgetsUpdate(Context context, Troll troll) {

        try {
            AppWidgetManager widgetManager = AppWidgetManager.getInstance(context);

            ComponentName componentName = new ComponentName(context, HomeScreenWidget.class);
            int[] appWidgetIds = widgetManager.getAppWidgetIds(componentName);

            if (appWidgetIds != null && appWidgetIds.length > 0) {

                // FIXME AThimel 14/02/14 Remove ASAP
                StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
                StrictMode.setThreadPolicy(policy);

                String dlaText = Trolls.getWidgetDlaTextFunction(context).apply(troll);
                Bitmap blason = MhDlaNotifierUtils.loadBlasonForWidget(troll.getBlason(), context.getCacheDir());

                for (int appWidgetId : appWidgetIds) {

                    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.home_screen_widget);
                    views.setTextViewText(R.id.widgetDla, dlaText);

                    if (blason == null) {
                        views.setImageViewResource(R.id.widgetImage, R.drawable.trarnoll_square_transparent_128);
                    } else {
                        views.setImageViewBitmap(R.id.widgetImage, blason);
                    }

                    // Tell the AppWidgetManager to perform an update on the current app widget
                    widgetManager.updateAppWidget(appWidgetId, views);
                }
            }

        } catch (Exception eee) {
            Log.e(TAG, "Unable to update widget(s)", eee);
        }

    }

    protected Pair<Boolean, Boolean> soundAndVibrate(Context context, PreferencesHolder preferences) {
        switch (preferences.silentNotification) {
        case ALWAYS: { // Toujours silencieux (pas de son, pas de vibration)
            Pair<Boolean, Boolean> result = Pair.of(false, false);
            return result;
        }
        case NEVER: { // Jamais silencieux (son + vibration)
            Pair<Boolean, Boolean> result = Pair.of(true, true);
            return result;
        }
        case BY_NIGHT: { // Ni son, ni vibration entre 23h et 7h
            Calendar now = Calendar.getInstance();
            int hour = now.get(Calendar.HOUR_OF_DAY);
            boolean soundAndVibrate = hour >= 7 && hour < 23;
            Pair<Boolean, Boolean> result = Pair.of(soundAndVibrate, soundAndVibrate);
            return result;
        }
        case WHEN_SILENT: { // Dpend du systme
            AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
            int ringerMode = am.getRingerMode();
            Pair<Boolean, Boolean> result = Pair.of(ringerMode == AudioManager.RINGER_MODE_NORMAL, // son
                    ringerMode == AudioManager.RINGER_MODE_NORMAL || ringerMode == AudioManager.RINGER_MODE_VIBRATE // vibration
            );
            return result;
        }
        default:
            Log.w(TAG, "Unexpected mode : " + preferences.silentNotification);
            throw new IllegalStateException("Unexpected mode : " + preferences.silentNotification);
        }
    }

    enum NotificationType {
        DLA, PV_LOSS
    }

    protected void notifyCurrentDlaAboutToExpire(Context context, Date dla, Integer pa,
            PreferencesHolder preferences) {
        CharSequence notifTitle = context.getText(R.string.current_dla_expiring_title);
        if (pa != null && pa == 0) {
            notifTitle = context.getText(R.string.current_dla_expiring_title_noPA);
        }
        String format = context.getText(R.string.current_dla_expiring_text).toString();
        CharSequence notifText = String.format(format, MhDlaNotifierUtils.formatHour(dla), pa);

        Pair<Boolean, Boolean> soundAndVibrate = soundAndVibrate(context, preferences);

        displayNotification(context, NotificationType.DLA, notifTitle, notifText, soundAndVibrate);
    }

    protected void notifyNextDlaNotActivated(Context context, Date dla, PreferencesHolder preferences) {
        CharSequence notifTitle = context.getText(R.string.next_dla_not_activated_title);
        String format = context.getText(R.string.next_dla_not_activated_text).toString();
        CharSequence notifText = String.format(format, MhDlaNotifierUtils.formatHour(dla));

        Pair<Boolean, Boolean> soundAndVibrate = soundAndVibrate(context, preferences);

        displayNotification(context, NotificationType.DLA, notifTitle, notifText, soundAndVibrate);
    }

    protected void notifyNextDlaAboutToExpire(Context context, Date dla, PreferencesHolder preferences) {
        CharSequence notifTitle = context.getText(R.string.next_dla_expiring_title);
        String format = context.getText(R.string.next_dla_expiring_text).toString();
        CharSequence notifText = String.format(format, MhDlaNotifierUtils.formatHour(dla));

        Pair<Boolean, Boolean> soundAndVibrate = soundAndVibrate(context, preferences);

        displayNotification(context, NotificationType.DLA, notifTitle, notifText, soundAndVibrate);
    }

    protected void notifyPvLoss(Context context, int pvLoss, int pv, PreferencesHolder preferences) {
        String titleFormat = context.getText(R.string.pv_loss_title).toString();
        CharSequence notifTitle = String.format(titleFormat, pvLoss);
        String messageFormat = context.getText(R.string.pv_remaining_text).toString();
        CharSequence notifText = String.format(messageFormat, pv);

        Pair<Boolean, Boolean> soundAndVibrate = soundAndVibrate(context, preferences);

        displayNotification(context, NotificationType.PV_LOSS, notifTitle, notifText, soundAndVibrate);
    }

    protected void displayNotification(Context context, NotificationType type, CharSequence title,
            CharSequence text, Pair<Boolean, Boolean> soundAndVibrate) {

        // The PendingIntent to launch our activity if the user selects this notification
        Intent mainActivity = new Intent(context, MainActivity.class);
        mainActivity.putExtra(MainActivity.EXTRA_FROM_NOTIFICATION, true);
        PendingIntent contentIntent = PendingIntent.getActivity(context, 0, mainActivity, 0);

        Intent playIntent = MhDlaNotifierUtils.GET_PLAY_INTENT.apply(context);
        PendingIntent playPendingIntent = PendingIntent.getActivity(context, 0, playIntent, 0);
        long now = System.currentTimeMillis();

        Bitmap largeIcon = BitmapFactory.decodeResource(context.getResources(),
                R.drawable.trarnoll_square_transparent_128);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(context).setOnlyAlertOnce(true)
                .setLights(Color.YELLOW, 1500, 1500).setSmallIcon(R.drawable.trarnoll_square_transparent_128)
                .setLargeIcon(largeIcon).setContentTitle(title).setContentText(text).setWhen(now)
                .setContentIntent(contentIntent).setAutoCancel(true).setTicker(title)
                .addAction(R.drawable.ic_action_play, context.getText(R.string.play), playPendingIntent);

        if (soundAndVibrate.left()) {
            Uri defaultSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
            builder.setSound(defaultSound);
        }

        if (soundAndVibrate.right()) {
            builder.setVibrate(VIBRATE_PATTERN);
        } else {
            builder.setVibrate(NO_VIBRATION);
        }

        Notification notification = builder.build();

        NotificationManager notificationManager = (NotificationManager) context
                .getSystemService(Context.NOTIFICATION_SERVICE);
        int notifId = type.name().hashCode();
        notificationManager.notify(notifId, notification);
    }

}