com.uphyca.idobata.android.service.IdobataService.java Source code

Java tutorial

Introduction

Here is the source code for com.uphyca.idobata.android.service.IdobataService.java

Source

/*
 * Copyright (C) 2014 uPhyca Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.uphyca.idobata.android.service;

import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import com.uphyca.idobata.ErrorListener;
import com.uphyca.idobata.Idobata;
import com.uphyca.idobata.IdobataError;
import com.uphyca.idobata.IdobataStream;
import com.uphyca.idobata.android.InjectionUtils;
import com.uphyca.idobata.android.R;
import com.uphyca.idobata.android.data.api.BackoffPolicy;
import com.uphyca.idobata.android.data.api.Environment;
import com.uphyca.idobata.android.data.api.MessageFilter;
import com.uphyca.idobata.android.data.api.NotificationsEffectsLEDFlash;
import com.uphyca.idobata.android.data.api.NotificationsEffectsSound;
import com.uphyca.idobata.android.data.api.NotificationsEffectsVibrate;
import com.uphyca.idobata.android.data.api.PollingInterval;
import com.uphyca.idobata.android.data.api.StreamConnection;
import com.uphyca.idobata.android.data.prefs.BooleanPreference;
import com.uphyca.idobata.android.data.prefs.LongPreference;
import com.uphyca.idobata.android.ui.MainActivity;
import com.uphyca.idobata.event.ConnectionEvent;
import com.uphyca.idobata.event.MessageCreatedEvent;
import com.uphyca.idobata.event.RoomTouchedEvent;
import com.uphyca.idobata.model.Message;
import com.uphyca.idobata.model.MessageBean;
import com.uphyca.idobata.model.User;

import java.io.IOException;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

/**
 * @author Sosuke Masui (masui@uphyca.com)
 */
public class IdobataService extends Service implements IdobataStream.ConnectionListener, ErrorListener {

    private static final int OPEN = 0;
    private static final long DELAY_MILLIS_TO_RESTART = TimeUnit.SECONDS.toMillis(5);

    @Inject
    Idobata mIdobata;

    @Inject
    AlarmManager mAlarmManager;

    @Inject
    NotificationManager mNotificationManager;

    @Inject
    Set<MessageFilter> mMessageFilters;

    @Inject
    @PollingInterval
    LongPreference mPollingIntervalPref;

    @Inject
    @NotificationsEffectsVibrate
    BooleanPreference mNotificationEffectsVibratePref;

    @Inject
    @NotificationsEffectsLEDFlash
    BooleanPreference mNotificationEffectsLEDFlashPref;

    @Inject
    @NotificationsEffectsSound
    BooleanPreference mNotificationEffectsSoundPref;

    @Inject
    Environment mEnvironment;

    @Inject
    @StreamConnection
    BackoffPolicy mBackoffPolicy;

    private Looper mServiceLooper;

    private ServiceHandler mServiceHandler;

    private User mUser;

    private IdobataStream mStream;

    @Override
    public void onCreate() {
        super.onCreate();
        ensureHandler();
        InjectionUtils.getObjectGraph(this).inject(this);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        executeAt(mEnvironment.elapsedRealtime() + mPollingIntervalPref.get());
        mServiceHandler.sendEmptyMessage(OPEN);
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        cancelPolling();
        closeQuietly();
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void closed(ConnectionEvent event) {
        retryToConnect();
    }

    @Override
    public void opened(ConnectionEvent event) {
        mBackoffPolicy.reset();
        try {
            notifyUnreadMessages();
        } catch (IdobataError idobataError) {
            idobataError.printStackTrace();
        }
    }

    @Override
    public void onError(IdobataError error) {
        retryToConnect();
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            return;
        }
        long cur = mEnvironment.elapsedRealtime();
        executeAt(cur + DELAY_MILLIS_TO_RESTART);
    }

    private void onMessageCreatedEvent(MessageCreatedEvent event) {
        Message message = eventToMessage(event);
        filterAndNotifyMessage(message, event.getOrganizationSlug(), event.getRoomName());
    }

    private void onRoomTouchedEvent(RoomTouchedEvent event) {
        Log.i("Idobata", "roomTouched: " + event);
    }

    private Message eventToMessage(MessageCreatedEvent event) {
        Message message = new MessageBean();
        message.setBody(event.getBody());
        message.setRoomId(event.getRoomId());
        message.setBodyPlain(event.getBodyPlain());
        message.setSenderName(event.getSenderName());
        message.setSenderId(event.getSenderId());
        message.setMentions(event.getMentions());
        message.setCreatedAt(event.getCreatedAt());
        message.setId(event.getId());
        message.setImageUrls(event.getImageUrls());
        message.setMultiline(event.isMultiline());
        message.setSenderIconUrl(event.getSenderIconUrl());
        message.setSenderType(event.getSenderType());
        return message;
    }

    //FIXME https://github.com/uPhyca/IdobataAndroid/issues/3
    private void notifyUnreadMessages() throws IdobataError {
        //        List<String> unreadMessageIds = new ArrayList<String>();
        //        Seed seed = mIdobata.getSeed();
        //        Records records = seed.getRecords();
        //        List<Room> rooms = records.getRooms();
        //        List<Organization> organizations = records.getOrganizations();
        //        for (Room room : rooms) {
        //            unreadMessageIds.addAll(room.getUnreadMessageIds());
        //        }
        //
        //        List<Message> unreadMessages = mIdobata.getMessages(unreadMessageIds);
        //        for (Message message : unreadMessages) {
        //
        //            //bodyPlain is always null when getting message from /api/messages
        //            message.setBodyPlain(stripTags(message.getBody()));
        //
        //            Room room = findRoomById(message.getRoomId(), rooms);
        //            Organization organization = findOrganizationById(room.getOrganizationId(), organizations);
        //            filterAndNotifyMessage(message, organization.getSlug(), room.getName());
        //        }
    }

    private String stripTags(String s) {
        return s.replaceAll("<(\"[^\"]*\"|'[^']*'|[^'\">])*>", "");
    }

    private void filterAndNotifyMessage(Message message, String organizationSlug, String roomName) {
        for (MessageFilter each : mMessageFilters) {
            if (!each.isSubscribed(mUser, message)) {
                return;
            }
        }
        notifyMessage(message, organizationSlug, roomName);
    }

    private void cancelPolling() {
        PendingIntent pi = buildPendingStartServiceIntent();
        mAlarmManager.cancel(pi);
    }

    private void retryToConnect() {
        if (mBackoffPolicy.isFailed()) {
            stopSelf();
            return;
        }

        long nextBackOffMillis = mBackoffPolicy.getNextBackOffMillis();
        try {
            executeAt(nextBackOffMillis);
        } finally {
            mBackoffPolicy.backoff();
        }
    }

    private void closeQuietly() {
        if (mStream == null) {
            return;
        }
        try {
            mStream.setConnectionListener(null).setErrorListener(null).close();
            mStream = null;
        } catch (IOException ignore) {
        }
    }

    private void executeAt(long triggerAtMillis) {
        PendingIntent pi = buildPendingStartServiceIntent();
        mAlarmManager.cancel(pi);
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, triggerAtMillis, pi);
    }

    private void ensureHandler() {
        HandlerThread t = new HandlerThread("IdobataService", Process.THREAD_PRIORITY_BACKGROUND);
        t.start();
        mServiceLooper = t.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    private void open() throws IdobataError {
        if (mUser == null) {
            mUser = mIdobata.getSeed().getRecords().getUser();
        }
        if (mStream == null) {
            mStream = mIdobata.openStream().setErrorListener(this).setConnectionListener(this)
                    .subscribeMessageCreated(new MessageCreatedEventListener())
                    .subscribeRoomTouched(new RoomTouchedEventListener());
        }
        mStream.open();
    }

    private CharSequence buildTitle(String organizationSlug, String roomName) {
        return new StringBuilder().append(organizationSlug).append(' ').append('/').append(' ').append(roomName);
    }

    private CharSequence buildText(Message message) {
        return new StringBuilder().append(message.getSenderName()).append(':').append(' ')
                .append(message.getBodyPlain()).toString();
    }

    private PendingIntent buildPendingStartServiceIntent() {
        final Intent intent = new Intent(IdobataService.this, IdobataService.class);
        return PendingIntent.getService(IdobataService.this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

    private PendingIntent buildPendingStartActivityIntent(Message message, String organizationSlug,
            String roomName) {

        final Intent intent = new Intent(IdobataService.this, MainActivity.class);
        intent.setAction(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_LAUNCHER);
        intent.setData(Uri
                .parse(String.format("https://idobata.io/#/organization/%s/room/%s", organizationSlug, roomName)));
        return PendingIntent.getActivity(IdobataService.this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

    private Notification buildNotification(PendingIntent pi, CharSequence title, CharSequence text) {
        int vibrate = mNotificationEffectsVibratePref.get() ? Notification.DEFAULT_VIBRATE : 0;
        int ledFlash = mNotificationEffectsLEDFlashPref.get() ? Notification.DEFAULT_LIGHTS : 0;
        int sound = mNotificationEffectsSoundPref.get() ? Notification.DEFAULT_SOUND : 0;
        NotificationCompat.Builder builder = new NotificationCompat.Builder(IdobataService.this)
                .setSmallIcon(R.drawable.ic_stat_notification).setContentTitle(title).setContentText(text)
                .setTicker(new StringBuilder(title).append(' ').append(text)).setAutoCancel(true)
                .setContentIntent(pi).setDefaults(vibrate | sound | ledFlash);
        if (ledFlash != 0) {
            builder.setLights(0xFF00FF00, 200, 1000);
        }
        return builder.build();
    }

    private void notifyMessage(Message message, String organizationSlug, String roomName) {
        PendingIntent pi = buildPendingStartActivityIntent(message, organizationSlug, roomName);
        CharSequence title = buildTitle(organizationSlug, roomName);
        CharSequence text = buildText(message);
        Notification notification = buildNotification(pi, title, text);
        mNotificationManager.notify(R.string.app_name, notification);
    }

    private final class MessageCreatedEventListener implements IdobataStream.Listener<MessageCreatedEvent> {

        @Override
        public void onEvent(MessageCreatedEvent event) {
            onMessageCreatedEvent(event);
        }
    }

    private final class RoomTouchedEventListener implements IdobataStream.Listener<RoomTouchedEvent> {
        @Override
        public void onEvent(RoomTouchedEvent event) {
            onRoomTouchedEvent(event);
        }
    }

    private final class ServiceHandler extends Handler {

        private ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(android.os.Message msg) {
            try {
                open();
            } catch (IdobataError idobataError) {
                onError(idobataError);
            }
        }
    }
}