it.mb.whatshare.GCMIntentService.java Source code

Java tutorial

Introduction

Here is the source code for it.mb.whatshare.GCMIntentService.java

Source

/**
 * GCMIntentService.java Created on 19 Jun 2013 Copyright 2013 Michele Bonazza
 * <emmepuntobi@gmail.com>
 * 
 * This file is part of WhatsHare.
 * 
 * WhatsHare 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.
 * 
 * Foobar 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
 * WhatsHare. If not, see <http://www.gnu.org/licenses/>.
 */
package it.mb.whatshare;

import it.mb.whatshare.MainActivity.PairedDevice;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.StreamCorruptedException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;

import com.google.android.gcm.GCMBaseIntentService;
import com.google.android.gcm.GCMRegistrar;

/**
 * Issues notifications to the system whenever messages come from GCM.
 * 
 * @author Michele Bonazza
 */
public class GCMIntentService extends GCMBaseIntentService {

    /**
     * Lock to synchronize with {@link GCMIntentService}.
     */
    private static final Lock REGISTRATION_LOCK = new ReentrantLock();
    private static final String PROJECT_SERVER_ID = "213874322054";
    private static final String UNKNOWN_GCM_ERROR = "UNKNOWN_GCM_ERROR";
    private static final Map<String, Integer> ERROR_CODES = new HashMap<String, Integer>() {
        private static final long serialVersionUID = 649852390172936228L;

        {
            put("ACCOUNT_MISSING", R.string.account_missing);
            put("TOO_MANY_REGISTRATIONS", R.string.too_many_registrations);
            put("INVALID_SENDER", R.string.invalid_sender);
            put("PHONE_REGISTRATION_ERROR", R.string.phone_registration_error);
            put(UNKNOWN_GCM_ERROR, R.string.unknown_gcm_error);
        }
    };

    /**
     * Condition that is released when {@link GCMIntentService} is done with the
     * registration process.
     */
    private static final Condition REGISTERED = REGISTRATION_LOCK.newCondition();

    private static AtomicInteger counter = new AtomicInteger();
    private static String registrationID = "";
    private static int errorID = -1;

    private Set<String> senderWhitelist = new HashSet<String>();
    private long lastCheckedWhitelist;

    /**
     * Creates a new intent service for the application.
     */
    public GCMIntentService() {
        super(PROJECT_SERVER_ID);
    }

    /**
     * Registers the device with the GCM server.
     * 
     * @param activity
     *            the calling activity
     */
    static void registerWithGCM(Context activity) {
        if ("".equals(registrationID)) {
            GCMRegistrar.checkDevice(activity);
            GCMRegistrar.checkManifest(activity);
            // register using the project ID
            GCMRegistrar.register(activity, PROJECT_SERVER_ID);
        }
    }

    /**
     * Returns the registration ID for this device, waiting for GCM to give this
     * device one if it hasn't done so before.
     * 
     * @return the registration ID
     * @throws CantRegisterWithGCMException
     *             in case an error message is received from GCM when trying to
     *             register this device
     */
    static String getRegistrationID() throws CantRegisterWithGCMException {
        if ("".equals(registrationID)) {
            try {
                REGISTRATION_LOCK.lock();
                // check again within lock
                if ("".equals(registrationID) && errorID == -1) {
                    REGISTERED.await();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                REGISTRATION_LOCK.unlock();
                if (errorID != -1) {
                    int error = errorID;
                    errorID = -1;
                    throw new CantRegisterWithGCMException(error);
                }
            }
        }
        return registrationID;
    }

    private void readWhitelist() {
        File whitelist = new File(getFilesDir(), MainActivity.INBOUND_DEVICES_FILENAME);
        // if whitelist doesn't exist, don't bother
        long lastModified = whitelist.exists() ? whitelist.lastModified() : Long.MIN_VALUE;

        if (lastCheckedWhitelist < lastModified) {
            FileInputStream fis = null;
            try {
                fis = openFileInput(MainActivity.INBOUND_DEVICES_FILENAME);
                ObjectInputStream ois = new ObjectInputStream(fis);
                Object read = ois.readObject();

                @SuppressWarnings("unchecked")
                List<PairedDevice> devices = (ArrayList<PairedDevice>) read;
                senderWhitelist = new HashSet<String>();
                for (PairedDevice device : devices) {
                    try {
                        senderWhitelist.add(String.valueOf(device.id.hashCode()));
                    } catch (NullPointerException e) {
                        // backward compatibility... devices didn't have an ID
                        senderWhitelist.add(String.valueOf(device.name.hashCode()));
                    }
                }
            } catch (FileNotFoundException e) {
                // it's ok, no whitelist, all messages are rejected
            } catch (StreamCorruptedException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                // TODO here the error should be notified I guess
                e.printStackTrace();
            } finally {
                if (fis != null)
                    try {
                        fis.close();
                    } catch (IOException e) {
                        // can't do much...
                        e.printStackTrace();
                    }
            }
            // whatever happens, don't keep checking for nothing
            lastCheckedWhitelist = lastModified;
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.android.gcm.GCMBaseIntentService#onError(android.content.Context
     * , java.lang.String)
     */
    @Override
    protected void onError(Context arg0, String arg1) {
        Utils.checkDebug(arg0);
        try {
            REGISTRATION_LOCK.lock();
            Utils.debug("Cannot register: %s", arg1);
            registrationID = "";
            errorID = ERROR_CODES.get(UNKNOWN_GCM_ERROR);
            if (arg1 != null) {
                Integer error = ERROR_CODES.get(arg1);
                if (error != null)
                    errorID = error;
            }
            REGISTERED.signalAll();
        } finally {
            REGISTRATION_LOCK.unlock();
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.android.gcm.GCMBaseIntentService#onRecoverableError(android
     * .content.Context, java.lang.String)
     */
    @Override
    protected boolean onRecoverableError(Context context, String errorId) {
        Utils.checkDebug(context);
        return super.onRecoverableError(context, errorId);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.android.gcm.GCMBaseIntentService#onMessage(android.content
     * .Context, android.content.Intent)
     */
    @Override
    protected void onMessage(Context arg0, Intent arg1) {
        Utils.checkDebug(arg0);
        Bundle bundle = arg1.getExtras();
        String sender = bundle.getString("sender");
        Utils.debug("new incoming message from %s: %s", sender, bundle.getString("message"));
        readWhitelist();

        if (senderWhitelist.contains(sender)) {
            String type = bundle.getString(MainActivity.INTENT_TYPE_EXTRA);
            if (type == null) {
                type = MainActivity.SHARE_VIA_WHATSAPP_EXTRA;
            }
            generateNotification(arg0, bundle.getString("message"),
                    MainActivity.SHARE_VIA_WHATSAPP_EXTRA.equals(type));
        } else {
            Utils.debug("ignoring message from %s, not in the whitelist", sender);
        }
    }

    /**
     * Creates a notification informing the user that new content is to be
     * shared.
     * 
     * @param context
     *            the current application context
     * @param message
     *            the message to be shared
     * @param useWhatsapp
     *            whether the content should be shared bypassing the app choice
     *            dialog calling whatsapp directly instead
     */
    @SuppressWarnings("deprecation")
    public void generateNotification(Context context, String message, boolean useWhatsapp) {
        // @formatter:off
        String title = context.getString(R.string.whatshare);
        Class<?> dst = useWhatsapp ? SendToWhatsappActivity.class : SendToAppActivity.class;
        // setAction is called so filterEquals() always returns false for all
        // our intents
        Intent whatshareIntent = new Intent(context, dst).putExtra("message", message)
                .setAction(String.valueOf(System.currentTimeMillis())).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        PendingIntent intent = PendingIntent.getActivity(context, 0, whatshareIntent,
                Intent.FLAG_ACTIVITY_NEW_TASK);

        Notification notification = null;
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
                .setSmallIcon(useWhatsapp ? R.drawable.notification_icon : R.drawable.whatshare_logo_notification,
                        0)
                .setContentTitle(title).setContentText(message).setContentIntent(intent)
                .setDefaults(Notification.DEFAULT_ALL);
        // @formatter:on

        if (Build.VERSION.SDK_INT > 15) {
            notification = buildForJellyBean(builder);
        } else {
            notification = builder.getNotification();
        }
        notification.flags |= Notification.FLAG_AUTO_CANCEL;

        ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
                .notify(counter.incrementAndGet(), notification);
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private Notification buildForJellyBean(NotificationCompat.Builder builder) {
        return builder.build();
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.android.gcm.GCMBaseIntentService#onRegistered(android.content
     * .Context, java.lang.String)
     */
    @Override
    protected void onRegistered(Context arg0, String arg1) {
        Utils.checkDebug(arg0);
        try {
            REGISTRATION_LOCK.lock();
            Utils.debug("Now app is registered with ID %s", arg1);
            registrationID = arg1;
            REGISTERED.signalAll();
        } finally {
            REGISTRATION_LOCK.unlock();
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.google.android.gcm.GCMBaseIntentService#onUnregistered(android.content
     * .Context, java.lang.String)
     */
    @Override
    protected void onUnregistered(Context arg0, String arg1) {
        // nothing to do here
    }

}