de.msal.shoutemo.connector.GetPostsService.java Source code

Java tutorial

Introduction

Here is the source code for de.msal.shoutemo.connector.GetPostsService.java

Source

/*
 * Copyright 2013 Maximilian Salomon.
 *
 * 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 de.msal.shoutemo.connector;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Service;
import android.content.ContentProviderOperation;
import android.content.Intent;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v4.content.LocalBroadcastManager;
import android.text.format.Time;
import android.util.Log;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import de.msal.shoutemo.authenticator.AccountAuthenticator;
import de.msal.shoutemo.connector.model.Post;
import de.msal.shoutemo.db.ChatDb;
import de.msal.shoutemo.ui.login.LoginActivity;

/**
 *
 */
public class GetPostsService extends Service {

    private static final String TAG = "Shoutemo|GetPostsService";
    // everything for showing the udpate status to the user
    private LocalBroadcastManager broadcaster;
    public static final String INTENT_UPDATE = "de.msal.shoutemo.GetPostsService.UPDATING";
    public static final String INTENT_UPDATE_ENABLED = "de.msal.shoutemo.GetPostsService.UPDATING_ENABLED";
    // repeating task (get posts)
    private static long INTERVAL = 2500; // default: 2.5s
    private ScheduledExecutorService worker;
    // account handling
    private String mAuthToken;
    private AccountManager mAccountManager;
    private Account mAccount;

    @Override
    public void onCreate() {
        super.onCreate();

        broadcaster = LocalBroadcastManager.getInstance(this);

        mAccountManager = AccountManager.get(this);
        Account[] acc = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE);

        /* No account; push the user into adding one */
        if (acc.length == 0) {
            Log.v(TAG, "No suitable account found, directing user to add one.");
            mAccountManager.addAccount(AccountAuthenticator.ACCOUNT_TYPE, null, null, new Bundle(), null,
                    new AccountManagerCallback<Bundle>() {
                        @Override
                        public void run(AccountManagerFuture<Bundle> result) {
                            Bundle bundle;
                            try {
                                bundle = result.getResult();
                            } catch (OperationCanceledException e) {
                                e.printStackTrace();
                                return;
                            } catch (AuthenticatorException e) {
                                e.printStackTrace();
                                return;
                            } catch (IOException e) {
                                e.printStackTrace();
                                return;
                            }

                            /* no accounts saved, yet; ask the user for credentials */
                            Intent launch = bundle.getParcelable(AccountManager.KEY_INTENT);
                            if (launch != null) {
                                launch.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                startActivity(launch);
                                return;
                            }

                            mAccount = new Account(bundle.getString(AccountManager.KEY_ACCOUNT_NAME),
                                    bundle.getString(AccountManager.KEY_ACCOUNT_TYPE));
                            Log.v(TAG, "Added account " + mAccount.name + "; now fetching new posts.");
                            startGetPostsTask();
                        }
                    }, null);
        } else {
            mAccount = acc[0];
            startGetPostsTask();
        }
    }

    @Override
    public void onDestroy() {
        stopGetPostsTask();
    }

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

    /**
     *
     */
    private void startGetPostsTask() {
        mAccountManager.getAuthToken(mAccount, LoginActivity.PARAM_AUTHTOKEN_TYPE, null, false,
                new AccountManagerCallback<Bundle>() {
                    @Override
                    public void run(AccountManagerFuture<Bundle> result) {
                        Bundle bundle;
                        try {
                            bundle = result.getResult();
                        } catch (OperationCanceledException e) {
                            e.printStackTrace();
                            return;
                        } catch (AuthenticatorException e) {
                            e.printStackTrace();
                            return;
                        } catch (IOException e) {
                            e.printStackTrace();
                            return;
                        }
                        mAuthToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
                        Log.v(TAG, "Received authentication token=" + mAuthToken);
                        // now get messages!
                        if (worker == null || worker.isShutdown()) {
                            worker = Executors.newSingleThreadScheduledExecutor();
                        }
                        // fix (possible) wrong time-setting on autemo.com
                        worker.execute(new SetTimezoneTask());
                        // only now recieve messages (with right time)
                        worker.scheduleAtFixedRate(new GetPostsTask(), 0, INTERVAL, TimeUnit.MILLISECONDS);
                    }
                }, null);
    }

    /**
     *
     */
    private void stopGetPostsTask() {
        if (worker != null) {
            worker.shutdown();
        }
    }

    /**
     * Sets the {@code INTERVAL} of this service in which the refresh calls should occur. This
     * {@code INTERVAL} is dependant on the given {@code timeSinceLastPost}. If the latest post was
     * some hours ago, it is not necessary to update every some seconds, but maybe every half a
     * minute. <br/> After the new refresh rate is altered, the task will be stoped and restartet at
     * its new rate.
     *
     * @param timeSinceLastPost the time in ms between the last post and the current time on the
     *                          phone.
     * @return the {@code INTERVAL} which was set, in ms.
     */
    private long setIntervall(long timeSinceLastPost) {
        long oldInterval = INTERVAL;

        if (timeSinceLastPost < 120000) { // < 2min
            INTERVAL = 2500; //   2.5s
        } else if (timeSinceLastPost < 300000) { // 2min - 5min
            INTERVAL = 5000; //   5.0s
        } else if (timeSinceLastPost < 600000) { // 5min - 10min
            INTERVAL = 10000; // 10.0s
        } else { // > 10min
            INTERVAL = 15000; // 15.0s
        }

        if (INTERVAL != oldInterval && worker != null && !worker.isShutdown()) {
            worker.shutdown();
            worker = Executors.newSingleThreadScheduledExecutor();
            worker.scheduleAtFixedRate(new GetPostsTask(), 0, INTERVAL, TimeUnit.MILLISECONDS);
        }

        return INTERVAL;
    }

    /**
     *
     */
    private class GetPostsTask extends Thread {

        List<Post> posts = null;

        @Override
        public void run() {
            setUpdatingNotification(true);

            try {
                posts = Connection.getPosts(mAuthToken);
            } catch (IOException e) {
                Log.e(TAG, e.getMessage());
            }

            /* check if new posts can be received */
            if (posts.isEmpty()) {
                Log.v(TAG, "Received empty data. Invalidating authtoken & fetching new one.");
                stopGetPostsTask();
                mAccountManager.invalidateAuthToken(AccountAuthenticator.ACCOUNT_TYPE, mAuthToken);
                startGetPostsTask();
            }

            /* insert into DB */
            ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
            for (Post post : posts) {
                if (post.getAuthor() != null) {
                    ops.add(ContentProviderOperation.newInsert(ChatDb.Authors.CONTENT_URI)
                            .withValue(ChatDb.Authors.COLUMN_NAME_NAME, post.getAuthor().getName())
                            .withValue(ChatDb.Authors.COLUMN_NAME_TYPE, post.getAuthor().getType().name())
                            .withYieldAllowed(true).build());
                }
                if (post.getMessage() != null) {
                    ops.add(ContentProviderOperation.newInsert(ChatDb.Messages.CONTENT_URI)
                            .withValue(ChatDb.Messages.COLUMN_NAME_AUTHOR_NAME,
                                    post.getAuthor() == null ? null : post.getAuthor().getName())
                            .withValue(ChatDb.Messages.COLUMN_NAME_MESSAGE_HTML, post.getMessage().getHtml())
                            .withValue(ChatDb.Messages.COLUMN_NAME_MESSAGE_TEXT, post.getMessage().getText())
                            .withValue(ChatDb.Messages.COLUMN_NAME_TYPE, post.getMessage().getType().name())
                            .withValue(ChatDb.Messages.COLUMN_NAME_TIMESTAMP, post.getDate().getTime())
                            .withYieldAllowed(true).build());
                }
            }
            try {
                getContentResolver().applyBatch(ChatDb.AUTHORITY, ops);
            } catch (RemoteException e) {
                Log.e(TAG, "Error: " + e.getMessage());
            } catch (OperationApplicationException e) {
                Log.e(TAG, "Error: " + e.getMessage());
            }

            /* dynamically alter the refresh rate */
            Cursor c = getContentResolver().query(ChatDb.Messages.CONTENT_URI,
                    new String[] { ChatDb.Messages.COLUMN_NAME_TIMESTAMP }, null, null,
                    ChatDb.Messages.COLUMN_NAME_TIMESTAMP + " DESC LIMIT 1");
            c.moveToFirst();
            long newestPostTimestamp = c.getLong(0);
            c.close();
            Time now = new Time();
            now.setToNow();
            long timeSinceLastPost = now.toMillis(false) - newestPostTimestamp;
            setIntervall(timeSinceLastPost);

            setUpdatingNotification(false);
        }
    }

    private class SetTimezoneTask extends Thread {

        @Override
        public void run() {
            /* have to add the dst savings, else during dst the time is off by another hour! */
            double offsetinHours = (TimeZone.getDefault().getOffset(new Date().getTime())
                    + TimeZone.getDefault().getDSTSavings()) / 1000.0 / 60 / 60;
            int returnCode = -2;
            try {
                returnCode = Connection.setUserTimezone(mAuthToken, offsetinHours);
            } catch (IOException e) {
                Log.e(TAG, e.getMessage());
            }
            if (returnCode != 200) {
                Log.e(TAG, "Error setting the timezone. Returned code=" + returnCode);
            }
        }
    }

    /**
     * Sends a {@code Intent} with the only data included whether this Service is currently trying
     * to get new {@code Post}s from the server.
     *
     * @param enabled Set to {@code true} when it is currently trying, to false when the Service is
     *                idling.
     */
    private void setUpdatingNotification(boolean enabled) {
        Intent intent = new Intent(INTENT_UPDATE);
        intent.putExtra(INTENT_UPDATE_ENABLED, enabled);
        broadcaster.sendBroadcast(intent);
    }

}