com.nttec.everychan.ui.tabs.TabsTrackerService.java Source code

Java tutorial

Introduction

Here is the source code for com.nttec.everychan.ui.tabs.TabsTrackerService.java

Source

/*
 * Everychan Android (Meta Imageboard Client)
 * Copyright (C) 2014-2016  miku-nyan <https://github.com/miku-nyan>
 *     
 * 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/>.
 */

package com.nttec.everychan.ui.tabs;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.LockSupport;

import org.apache.commons.lang3.tuple.Triple;

import com.nttec.everychan.R;
import com.nttec.everychan.api.ChanModule;
import com.nttec.everychan.api.interfaces.CancellableTask;
import com.nttec.everychan.api.interfaces.CancellableTask.BaseCancellableTask;
import com.nttec.everychan.api.models.UrlPageModel;
import com.nttec.everychan.api.util.PageLoaderFromChan;
import com.nttec.everychan.cache.PagesCache;
import com.nttec.everychan.cache.SerializablePage;
import com.nttec.everychan.common.Async;
import com.nttec.everychan.common.Logger;
import com.nttec.everychan.common.MainApplication;
import com.nttec.everychan.http.interactive.InteractiveException;
import com.nttec.everychan.ui.MainActivity;
import com.nttec.everychan.ui.downloading.BackgroundThumbDownloader;
import com.nttec.everychan.ui.presentation.BoardFragment;
import com.nttec.everychan.ui.presentation.PresentationModel;
import com.nttec.everychan.ui.settings.ApplicationSettings;
import com.nttec.everychan.ui.settings.Wifi;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Build;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;

/**
 * ? ?
 * @author miku-nyan
 *
 */
public class TabsTrackerService extends Service {
    private static final String TAG = "TabsTrackerService";

    public static final String EXTRA_UPDATE_IMMEDIATELY = "UpdateImmediately";
    public static final String EXTRA_CLEAR_SUBSCRIPTIONS = "ClearSubscriptions";
    public static final String BROADCAST_ACTION_NOTIFY = "com.nttec.everychan.BROADCAST_ACTION_TRACKER_NOTIFY";
    public static final String BROADCAST_ACTION_CLEAR_SUBSCRIPTIONS = "com.nttec.everychan.BROADCAST_ACTION_CLEAR_SUBSCRIPTIONS";
    public static final int TRACKER_NOTIFICATION_UPDATE_ID = 40;
    public static final int TRACKER_NOTIFICATION_SUBSCRIPTIONS_ID = 50;

    /** true, ? ?? ??  */
    private static boolean running = false;
    /** ? true,   ?  ? "?  ??" */
    private static boolean unread = false;
    /** ? true, ??     ? ? */
    private static boolean subscriptions = false;
    /** ?? ,   ?   ? ? (triple: url , url ? ??  ?,  ) */
    private static List<Triple<String, String, String>> subscriptionsData = null;
    /** ID , ? ???     -1 */
    private static long currentUpdatingTabId = -1;

    /** true, ? ?? ??  */
    public static boolean isRunning() {
        return running;
    }

    /**  ,   ?   ? ? (  ) */
    public static void addSubscriptionNotification(String tabUrl, String postNumber, String tabTitle) {
        List<Triple<String, String, String>> list = subscriptionsData;
        if (list == null)
            list = new ArrayList<>();
        int index = findTab(list, tabUrl, tabTitle);
        if (index == -1) {
            String postUrl = tabUrl;
            try {
                UrlPageModel pageModel = UrlHandler.getPageModel(tabUrl);
                if (pageModel != null) {
                    pageModel.postNumber = postNumber;
                    postUrl = MainApplication.getInstance().getChanModule(pageModel.chanName).buildUrl(pageModel);
                }
            } catch (Exception e) {
                Logger.e(TAG, e);
            }
            list.add(Triple.of(tabUrl, postUrl, tabTitle));
        } else {
            String postUrl = list.get(index).getMiddle();
            list.set(index, Triple.of(tabUrl, postUrl, tabTitle));
        }
        subscriptionsData = list;
        subscriptions = true;
    }

    /** ?   ?:   ?    ? "?  ??" */
    public static void setUnread() {
        unread = true;
    }

    /** ? ??? ?  :  ? "?  ??" */
    public static void clearUnread() {
        unread = false;
    }

    /** ? ?? ,   ?   ? ?,    ?
     *  ( ?, ?   ?      ?,    ?) */
    public static void clearSubscriptions() {
        subscriptions = false;
        subscriptionsData = null;
    }

    /**  ID , ? ???   ;  -1, ?   ???    */
    public static long getCurrentUpdatingTabId() {
        return currentUpdatingTabId;
    }

    /** ??,   ; ?   ? ????  ? ,    */
    public static void onResumeTab(Context context, String tabUrl, String tabTitle) {
        List<Triple<String, String, String>> list = subscriptionsData;
        if (list == null)
            return;
        int index = findTab(list, tabUrl, tabTitle);
        if (index != -1) {
            ((NotificationManager) context.getSystemService(NOTIFICATION_SERVICE))
                    .cancel(TRACKER_NOTIFICATION_SUBSCRIPTIONS_ID);
            clearSubscriptions();
        }
    }

    private static int findTab(List<Triple<String, String, String>> list, String tabUrl, String tabTitle) {
        for (int i = 0; i < list.size(); ++i) {
            Triple<String, String, String> triple = list.get(i);
            if (tabUrl == null) {
                if (triple.getLeft() == null && tabTitle.equals(triple.getRight())) {
                    return i;
                }
            } else {
                if (tabUrl.equals(triple.getLeft())) {
                    return i;
                }
            }
        }
        return -1;
    }

    private ApplicationSettings settings;
    private TabsState tabsState;
    private TabsSwitcher tabsSwitcher;
    private PagesCache pagesCache;
    private NotificationManager notificationManager;
    private BroadcastReceiver broadcastReceiver;
    private boolean isForeground = false;

    private int timerDelay;
    private boolean enableNotification;
    private boolean backgroundTabs;

    private boolean immediately = false;

    private CancellableTask task = null;

    private void notifyForeground(int id, Notification notification) {
        if (!isForeground) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ECLAIR) {
                try {
                    getClass().getMethod("setForeground", new Class[] { boolean.class }).invoke(this, Boolean.TRUE);
                } catch (Exception e) {
                    Logger.e(TAG, "cannot invoke setForeground(true)", e);
                }
                notificationManager.notify(id, notification);
            } else {
                ForegroundCompat.startForeground(this, id, notification);
            }
            isForeground = true;
        } else {
            notificationManager.notify(id, notification);
        }
    }

    private void cancelForeground(int id) {
        if (isForeground) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ECLAIR) {
                notificationManager.cancel(id);
                try {
                    getClass().getMethod("setForeground", new Class[] { boolean.class }).invoke(this,
                            Boolean.FALSE);
                } catch (Exception e) {
                    Logger.e(TAG, "cannot invoke setForeground(false)", e);
                }
            } else {
                ForegroundCompat.stopForeground(this);
            }
            isForeground = false;
        } else {
            notificationManager.cancel(id);
        }
    }

    @TargetApi(Build.VERSION_CODES.ECLAIR)
    private static class ForegroundCompat {
        static void startForeground(Service service, int id, Notification notification) {
            service.startForeground(id, notification);
        }

        static void stopForeground(Service service) {
            service.stopForeground(true);
        }
    }

    @Override
    public void onCreate() {
        Logger.d(TAG, "TabsTrackerService creating");
        super.onCreate();
        notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        settings = MainApplication.getInstance().settings;
        tabsState = MainApplication.getInstance().tabsState;
        tabsSwitcher = MainApplication.getInstance().tabsSwitcher;
        pagesCache = MainApplication.getInstance().pagesCache;
        registerReceiver(broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                Logger.d(TAG, "received BROADCAST_ACTION_CLEAR_SUBSCRIPTIONS");
                clearSubscriptions();
            }
        }, new IntentFilter(BROADCAST_ACTION_CLEAR_SUBSCRIPTIONS));
    }

    @SuppressLint("InlinedApi")
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return Service.START_STICKY;
    }

    @Override
    public void onStart(Intent intent, int startId) {
        Logger.d(TAG, "TabsTrackerService starting");
        enableNotification = settings.isAutoupdateNotification();
        immediately = intent != null && intent.getBooleanExtra(EXTRA_UPDATE_IMMEDIATELY, false);
        backgroundTabs = settings.isAutoupdateBackground();
        timerDelay = settings.getAutoupdateDelay();
        if (running) {
            Logger.d(TAG, "TabsTrackerService service already running");
            return;
        }
        clearUnread();
        clearSubscriptions();
        TrackerLoop loop = new TrackerLoop();
        task = loop;
        Async.runAsync(loop);
        running = true;
    }

    private void doUpdate(final CancellableTask task) {
        if (backgroundTabs || immediately) {
            int tabsArrayLength = tabsState.tabsArray.size();
            TabModel[] tabsArray = new TabModel[tabsArrayLength]; //avoid of java.util.ConcurrentModificationException
            for (int i = 0; i < tabsArrayLength; ++i)
                tabsArray[i] = tabsState.tabsArray.get(i);
            for (final TabModel tab : tabsArray) {
                if (task.isCancelled())
                    return;
                if (settings.isAutoupdateWifiOnly() && !Wifi.isConnected() && !immediately)
                    return;
                if (tab.type == TabModel.TYPE_NORMAL && tab.pageModel.type == UrlPageModel.TYPE_THREADPAGE
                        && tab.autoupdateBackground) {
                    if (tabsSwitcher.currentId != null && tabsSwitcher.currentId.equals(tab.id))
                        continue;
                    final String hash = tab.hash;
                    ChanModule chan = MainApplication.getInstance().getChanModule(tab.pageModel.chanName);
                    currentUpdatingTabId = tab.id;
                    final PresentationModel presentationModel = pagesCache.getPresentationModel(hash);
                    final SerializablePage serializablePage;
                    if (presentationModel != null) {
                        serializablePage = presentationModel.source;
                    } else {
                        SerializablePage pageFromFilecache = pagesCache.getSerializablePage(hash);
                        if (pageFromFilecache != null) {
                            serializablePage = pageFromFilecache;
                        } else {
                            serializablePage = new SerializablePage();
                            serializablePage.pageModel = tab.pageModel;
                        }
                    }

                    final int oldCount = serializablePage.posts != null ? serializablePage.posts.length : 0;
                    new PageLoaderFromChan(serializablePage, new PageLoaderFromChan.PageLoaderCallback() {
                        @Override
                        public void onSuccess() {
                            BackgroundThumbDownloader.download(serializablePage, task);
                            MainApplication.getInstance().subscriptions.checkOwnPost(serializablePage, oldCount);
                            tab.autoupdateError = false;
                            int newCount = serializablePage.posts != null ? serializablePage.posts.length : 0;
                            if (oldCount != newCount) {
                                if (oldCount != 0)
                                    tab.unreadPostsCount += (newCount - oldCount);
                                setUnread();
                                int checkSubscriptions = MainApplication.getInstance().subscriptions
                                        .checkSubscriptions(serializablePage, oldCount);
                                if (checkSubscriptions >= 0) {
                                    addSubscriptionNotification(tab.webUrl,
                                            serializablePage.posts[checkSubscriptions].number, tab.title);
                                    tab.unreadSubscriptions = true;
                                }
                            }
                            if (presentationModel != null) {
                                presentationModel.setNotReady();
                                pagesCache.putPresentationModel(hash, presentationModel);
                            } else {
                                pagesCache.putSerializablePage(hash, serializablePage);
                            }
                        }

                        @Override
                        public void onInteractiveException(InteractiveException e) {
                            tab.autoupdateError = true;
                        }

                        @Override
                        public void onError(String message) {
                            tab.autoupdateError = true;
                        }
                    }, chan, task).run();
                }
            }
            currentUpdatingTabId = -1;
        }
        if (task.isCancelled())
            return;
        if (settings.isAutoupdateWifiOnly() && !Wifi.isConnected() && !immediately)
            return;
        if (tabsSwitcher.currentFragment instanceof BoardFragment) {
            TabModel tab = tabsState.findTabById(tabsSwitcher.currentId);
            if (tab != null && tab.pageModel != null && tab.type == TabModel.TYPE_NORMAL
                    && tab.pageModel.type == UrlPageModel.TYPE_THREADPAGE) {
                Async.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            ((BoardFragment) tabsSwitcher.currentFragment).updateSilent();
                        } catch (Exception e) {
                            Logger.e(TAG, e);
                        }
                    }
                });
            }
        }
    }

    private class TrackerLoop extends BaseCancellableTask implements Runnable {
        private int timerCounter = 0;

        @Override
        public void run() {
            while (true) {
                if (isCancelled()) {
                    cancelForeground(TRACKER_NOTIFICATION_UPDATE_ID);
                    return;
                }
                Notification subscriptionsNotification = getSubscriptionsNotification();
                if (subscriptionsNotification != null)
                    notificationManager.notify(TRACKER_NOTIFICATION_SUBSCRIPTIONS_ID, subscriptionsNotification);

                if (++timerCounter > timerDelay || immediately) {
                    timerCounter = 0;
                    if (enableNotification) {
                        notifyForeground(TRACKER_NOTIFICATION_UPDATE_ID, getUpdateNotification(-1));
                    }
                    if (!settings.isAutoupdateWifiOnly() || Wifi.isConnected() || immediately) {
                        doUpdate(this);
                        immediately = false;
                    }

                    if (isCancelled()) {
                        cancelForeground(TRACKER_NOTIFICATION_UPDATE_ID);
                        return;
                    } else {
                        sendBroadcast(new Intent(BROADCAST_ACTION_NOTIFY));
                    }

                    if (!settings.isAutoupdateEnabled())
                        stopSelf();

                } else {
                    if (enableNotification) {
                        int remainingTime = timerDelay - timerCounter + 1;
                        notifyForeground(TRACKER_NOTIFICATION_UPDATE_ID, getUpdateNotification(remainingTime));
                    }
                }

                LockSupport.parkNanos(1000000000);
            }
        }

        //? secondsRemaining == -1, ?  "??? "
        private Notification getUpdateNotification(int secondsRemaining) {
            return notifUpdate
                    .setContentTitle(
                            getString(unread ? R.string.tabs_tracker_title_unread : R.string.tabs_tracker_title))
                    .setContentText(secondsRemaining == -1 ? getString(R.string.tabs_tracker_updating)
                            : getResources().getQuantityString(R.plurals.tabs_tracker_timer, secondsRemaining,
                                    secondsRemaining))
                    .build();
        }

        private Notification getSubscriptionsNotification() {
            if (!subscriptions)
                return null;
            subscriptions = false;
            List<Triple<String, String, String>> list = subscriptionsData;
            if (list == null || list.size() == 0)
                return null;
            String url = list.get(0).getMiddle();
            Intent activityIntent = new Intent(TabsTrackerService.this, MainActivity.class)
                    .putExtra(EXTRA_CLEAR_SUBSCRIPTIONS, true);
            if (url != null)
                activityIntent.setData(Uri.parse(url));
            NotificationCompat.InboxStyle style = list.size() == 1 ? null
                    : new NotificationCompat.InboxStyle()
                            .addLine(getString(R.string.subscriptions_notification_text_format,
                                    list.get(0).getRight()))
                            .addLine(getString(R.string.subscriptions_notification_text_format,
                                    list.get(1).getRight()));
            if (list.size() > 2)
                style.setSummaryText(getString(R.string.subscriptions_notification_text_more, list.size() - 2));

            return notifSubscription
                    .setContentText(list.size() > 1 ? getString(R.string.subscriptions_notification_text_multiple)
                            : getString(R.string.subscriptions_notification_text_format, list.get(0).getRight()))
                    .setStyle(style).setContentIntent(PendingIntent.getActivity(TabsTrackerService.this, 0,
                            activityIntent, PendingIntent.FLAG_CANCEL_CURRENT))
                    .build();
        }

        private NotificationCompat.Builder notifUpdate = new NotificationCompat.Builder(TabsTrackerService.this)
                .setSmallIcon(R.drawable.ic_launcher).setCategory(NotificationCompat.CATEGORY_SERVICE)
                .setContentIntent(PendingIntent.getActivity(TabsTrackerService.this, 0,
                        new Intent(TabsTrackerService.this, MainActivity.class),
                        PendingIntent.FLAG_CANCEL_CURRENT));

        private NotificationCompat.Builder notifSubscription = new NotificationCompat.Builder(
                TabsTrackerService.this).setSmallIcon(R.drawable.ic_launcher)
                        .setDefaults(NotificationCompat.DEFAULT_ALL).setOngoing(false).setAutoCancel(true)
                        .setOnlyAlertOnce(true).setCategory(NotificationCompat.CATEGORY_MESSAGE)
                        .setContentTitle(getString(R.string.subscriptions_notification_title))
                        .setDeleteIntent(PendingIntent.getBroadcast(TabsTrackerService.this, 0,
                                new Intent(BROADCAST_ACTION_CLEAR_SUBSCRIPTIONS),
                                PendingIntent.FLAG_CANCEL_CURRENT));

    }

    @Override
    public void onDestroy() {
        Logger.d(TAG, "TabsTrackerService destroying");
        if (task != null)
            task.cancel();
        running = false;
        unregisterReceiver(broadcastReceiver);
    }

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

}