com.rowland.hashtrace.sync.TweetHashTracerSyncAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.rowland.hashtrace.sync.TweetHashTracerSyncAdapter.java

Source

/*
 *
 *  * Copyright (c) 2016 Oti Rowland
 *  *
 *  * 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.rowland.hashtrace.sync;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SyncRequest;
import android.content.SyncResult;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.annotation.IntDef;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;

import com.rowland.hashtrace.data.provider.TweetHashTracerContract;
import com.rowland.hashtrace.data.provider.TweetHashTracerContract.HashTagEntry;
import com.rowland.hashtrace.data.provider.TweetHashTracerContract.TweetEntry;
import com.rowland.hashtrace.ui.activities.MainActivity;
import com.rowland.hashtrace.R;
import com.rowland.hashtrace.models.Tweet;
import com.rowland.hashtrace.utility.EDbDateLimit;
import com.rowland.hashtrace.utility.Utility;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Vector;

import twitter4j.Query;
import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterException;

/**
 * @author Rowland
 *
 */
public class TweetHashTracerSyncAdapter extends AbstractThreadedSyncAdapter {

    // Interval at which to sync with the tweet, in milliseconds.
    // 60 seconds (1 minute) * 180 = 3 hours
    public static final int SYNC_INTERVAL = 60 * 180;
    public static final int SYNC_FLEXTIME = SYNC_INTERVAL / 3;
    public static final int HASHTAG_STATUS_OK = 0;
    public static final int HASHTAG_SERVER_DOWN = 1;
    public static final int HASHTAG_STATUS_SERVER_INVALID = 2;
    public static final int HASHTAG_STATUS_UNKNOWN = 3;
    private static final long DAY_IN_MILLIS = 1000 * 60 * 60 * 6;
    private static final int TWEET_NOTIFICATION_ID = 3004;
    private static final String[] NOTIFY_TWEET_PROJECTION = new String[] { TweetEntry.COLUMN_TWEET_ID,
            TweetEntry.COLUMN_TWEET_TEXT, TweetEntry.COLUMN_TWEET_TEXT_DATE, TweetEntry.COLUMN_TWEET_USERNAME,
            TweetEntry.COLUMN_TWEET_USERNAME_IMAGE_URL };
    // these indices must match the projection
    private static final int INDEX_TWEET_ID = 0;
    private static final int INDEX_TWEET_TEXT = 1;
    private static final int INDEX_TWEET_TEXT_DATE = 2;
    private static final int INDEX_TWEET_USER_NAME = 3;
    private static final int INDEX_TWEET_USER_NAME_IMAGE_URL = 4;
    public final String LOG_TAG = TweetHashTracerSyncAdapter.class.getSimpleName();
    private Twitter mTwitter;

    public TweetHashTracerSyncAdapter(Context context, boolean autoInitialize) {
        super(context, autoInitialize);
    }

    /**
     * Helper method to schedule the sync adapter periodic execution
     */
    public static void configurePeriodicSync(Context context, int syncInterval, int flexTime) {
        Account account = getSyncAccount(context);
        String authority = context.getString(R.string.content_authority);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            // we can enable inexact timers in our periodic sync
            SyncRequest request = new SyncRequest.Builder().syncPeriodic(syncInterval, flexTime)
                    .setSyncAdapter(account, authority).build();
            ContentResolver.requestSync(request);
        } else {
            ContentResolver.addPeriodicSync(account, authority, new Bundle(), syncInterval);
        }
    }

    /**
     * Helper method to have the sync adapter sync immediately
     *
     * @param context The context used to access the account service
     */
    public static void syncImmediately(Context context) {
        Bundle bundle = new Bundle();
        bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
        bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
        ContentResolver.requestSync(getSyncAccount(context), context.getString(R.string.content_authority), bundle);
    }

    /**
     * Helper method to get the fake account to be used with SyncAdapter, or
     * make a new one if the fake account doesn't exist yet. If we make a new
     * account, we call the onAccountCreated method so we can initialize things.
     *
     * @param context The context used to access the account service
     * @return a fake account.
     */
    public static Account getSyncAccount(Context context) {
        // Get an instance of the Android account manager
        AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);

        // Create the account type and default account
        Account newAccount = new Account(context.getString(R.string.app_name),
                context.getString(R.string.sync_account_type));

        // If the password doesn't exist, the account doesn't exist
        if (null == accountManager.getPassword(newAccount)) {

            /*
             * Add the account and account type, no password or user data If
             * successful, return the Account object, otherwise report an error.
             */
            if (!accountManager.addAccountExplicitly(newAccount, "", null)) {
                return null;
            }
            /*
             * If you don't set android:syncable="true" in in your <provider>
             * element in the manifest, then call
             * ContentResolver.setIsSyncable(account, AUTHORITY, 1) here.
             */

            onAccountCreated(newAccount, context);
        }

        return newAccount;
    }

    private static void onAccountCreated(Account newAccount, Context context) {
        /*
         * Since we've created an account
         */
        TweetHashTracerSyncAdapter.configurePeriodicSync(context, SYNC_INTERVAL, SYNC_FLEXTIME);

        /*
         * Without calling setSyncAutomatically, our periodic sync will not be
         * enabled.
         */
        ContentResolver.setSyncAutomatically(newAccount, context.getString(R.string.content_authority), true);

        /*
         * Finally, let's do a sync to get things started
         */
        syncImmediately(context);
    }

    public static void initializeSyncAdapter(Context context) {
        getSyncAccount(context);
    }

    @Override
    public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
            SyncResult syncResult) {
        Log.d(LOG_TAG, "Starting sync");
        // Getting the hashtag to send to the API
        String hashTagQuery = Utility.getPreferredHashTag(getContext());

        long hashTagId = addHashTag(hashTagQuery, hashTagQuery);

        ArrayList<Tweet> tweetArrayList = new ArrayList<Tweet>();
        List<Status> statuses = new ArrayList<Status>();

        try {

            mTwitter = Utility.getTwitter();

            statuses = mTwitter.search(new Query(hashTagQuery)).getTweets();

            for (Status s : statuses) {
                Tweet tweet = Utility.createTweet(s);
                tweetArrayList.add(tweet);
            }

        }

        catch (TwitterException e)

        {
            e.printStackTrace();
        }

        try {

            // Insert the new tweet information into the database
            Vector<ContentValues> cVVector = new Vector<ContentValues>(tweetArrayList.size());

            for (int i = 0; i < tweetArrayList.size(); i++) {
                // These are the values that will be collected.
                Tweet tweet = tweetArrayList.get(i);
                long tweet_id = tweet.getTweetId();
                String tweet_text = tweet.getTweetText();
                Date tweet_text_date = tweet.getTweetTextDate();
                int tweet_text_retweet_count = tweet.getTweetRetweetCount();
                int tweet_text_favourite_count = tweet.getTweetFavouriteCount();
                int tweet_text_mentions_count = tweet.getTweetMentionsCount();
                String user_name = tweet.getUserName();
                String user_image_url = tweet.getUserImageUrl();
                String user_cover_url = tweet.getUserCoverUrl();
                String user_location = tweet.getUserLocation();
                String user_description = tweet.getUserDescription();

                ContentValues tweetValues = new ContentValues();

                tweetValues.put(TweetEntry.COLUMN_HASHTAG_KEY, hashTagId);
                tweetValues.put(TweetEntry.COLUMN_TWEET_TEXT_DATE, TweetHashTracerContract
                        .getDbDateString(tweet_text_date, EDbDateLimit.DATE_FORMAT_NOW_LIMIT));
                tweetValues.put(TweetEntry.COLUMN_TWEET_ID, tweet_id);
                tweetValues.put(TweetEntry.COLUMN_TWEET_TEXT, tweet_text);
                tweetValues.put(TweetEntry.COLUMN_TWEET_TEXT_RETWEET_COUNT, tweet_text_retweet_count);
                tweetValues.put(TweetEntry.COLUMN_TWEET_TEXT_FAVOURITE_COUNT, tweet_text_favourite_count);
                tweetValues.put(TweetEntry.COLUMN_TWEET_TEXT_MENTIONS_COUNT, tweet_text_mentions_count);
                tweetValues.put(TweetEntry.COLUMN_TWEET_USERNAME, user_name);
                tweetValues.put(TweetEntry.COLUMN_TWEET_USERNAME_IMAGE_URL, user_image_url);
                tweetValues.put(TweetEntry.COLUMN_TWEET_USERNAME_COVER_URL, user_cover_url);
                tweetValues.put(TweetEntry.COLUMN_TWEET_USERNAME_LOCATION, user_location);
                tweetValues.put(TweetEntry.COLUMN_TWEET_USERNAME_DESCRIPTION, user_description);

                cVVector.add(tweetValues);
            }

            if (cVVector.size() > 0) {
                ContentValues[] cvArray = new ContentValues[cVVector.size()];
                cVVector.toArray(cvArray);
                int insertNo = getContext().getContentResolver().bulkInsert(TweetEntry.CONTENT_URI, cvArray);

                // Get's a calendar object with the current time.
                Calendar cal = Calendar.getInstance();
                cal.add(Calendar.DATE, -3); // Signifies 3 days ago's date
                String previousDate = TweetHashTracerContract.getDbDateString(cal.getTime(),
                        EDbDateLimit.DATE_FORMAT_NOW_LIMIT);
                getContext().getContentResolver().delete(TweetEntry.CONTENT_URI,
                        TweetEntry.COLUMN_TWEET_TEXT_DATE + " <= ?", new String[] { previousDate });

                notifyWeather();

            }
            Log.d(LOG_TAG, "FetchTweetTask Complete. " + cVVector.size() + " Inserted");

        } catch (Exception e) {
            Log.e(LOG_TAG, e.getMessage(), e);
            e.printStackTrace();
        }

        // This will only happen if there was an error getting or parsing the forecast.
        return;
    }

    /**
     * Helper method to handle insertion of a new hashtag in the tweet database.
     *
     * @param hashTagSetting
     *            The hashtag string used to request updates from the server.
     * @return the row ID of the added location.
     */
    private long addHashTag(String hashTagSetting, String hashTagName) {
        long hashTagId;

        Log.v(LOG_TAG, "inserting " + hashTagSetting + ", with name: " + hashTagName);

        // First, check if the location with this city name exists in the db
        Cursor hashTagCursor = getContext().getContentResolver().query(
                TweetHashTracerContract.HashTagEntry.CONTENT_URI, new String[] { HashTagEntry._ID },
                HashTagEntry.COLUMN_HASHTAG_SETTING + " = ?", new String[] { hashTagSetting }, null);

        if (hashTagCursor.moveToFirst()) {
            int hashTagIdIndex = hashTagCursor.getColumnIndex(HashTagEntry._ID);
            hashTagId = hashTagCursor.getLong(hashTagIdIndex);
        } else {
            // Now that the content provider is set up, inserting rows of data
            // is pretty simple.
            // First create a ContentValues object to hold the data you want to
            // insert.
            ContentValues hashTagValues = new ContentValues();

            // Then add the data, along with the corresponding name of the data
            // type,
            // so the content provider knows what kind of value is being
            // inserted.
            hashTagValues.put(HashTagEntry.COLUMN_HASHTAG_NAME, hashTagName);
            hashTagValues.put(HashTagEntry.COLUMN_HASHTAG_SETTING, hashTagSetting);

            // Finally, insert location data into the database.
            Uri insertedUri = getContext().getContentResolver()
                    .insert(TweetHashTracerContract.HashTagEntry.CONTENT_URI, hashTagValues);

            // The resulting URI contains the ID for the row. Extract the
            // locationId from the Uri.
            hashTagId = ContentUris.parseId(insertedUri);
        }

        // Wait, that worked? Yes!
        return hashTagId;
    }

    private void notifyWeather() {
        Context context = getContext();
        // checking the last update and notify if its the first of the day
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        String displayNotificationsKey = context.getString(R.string.pref_enable_notifications_key);
        boolean displayNotifications = prefs.getBoolean(displayNotificationsKey,
                Boolean.parseBoolean(context.getString(R.string.pref_enable_notifications_default)));

        if (displayNotifications) {
            String lastNotificationKey = context.getString(R.string.pref_last_notification);
            long lastSync = prefs.getLong(lastNotificationKey, 0);

            if (System.currentTimeMillis() - lastSync >= DAY_IN_MILLIS) {
                // Last sync was more than 1 day ago, let's send a notification with the tweet.
                String hashTagQuery = Utility.getPreferredHashTag(context);

                // Sort order:  Ascending, by date.
                //String sortOrder = TweetEntry.COLUMN_TWEET_TEXT_DATE + " ASC";

                Uri tweetUri = TweetEntry.buildTweetHashTagWithDate(hashTagQuery,
                        TweetHashTracerContract.getDbDateString(new Date(), EDbDateLimit.DATE_FORMAT_MINUTE_LIMIT));

                // we'll query our contentProvider, as always
                Cursor cursor = context.getContentResolver().query(tweetUri, NOTIFY_TWEET_PROJECTION, null, null,
                        null);

                if (cursor != null && cursor.moveToFirst()) {
                    int tweetId = cursor.getInt(INDEX_TWEET_ID);
                    String tweet_text = cursor.getString(INDEX_TWEET_TEXT);
                    String tweet_text_date = cursor.getString(INDEX_TWEET_TEXT_DATE);
                    String user_name = cursor.getString(INDEX_TWEET_USER_NAME);
                    String user_name_image_url = cursor.getString(INDEX_TWEET_USER_NAME_IMAGE_URL);

                    int smallIconId = Utility.getSmallIconResourceForTweetNotification(tweetId);
                    Bitmap largeIcon = Utility.getLargeIconResourceForTweetNotification(user_name_image_url,
                            context);

                    int notificationCount = 0;
                    String title = context.getString(R.string.app_name);

                    // Define the text of the tweet
                    String contentText = String.format(context.getString(R.string.format_notification), tweet_text);

                    // NotificationCompatBuilder is a very convenient way to backward-compatible
                    // notifications. Just throw in some data.
                    NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getContext())
                            .setSmallIcon(smallIconId).setContentTitle(title).setContentText(contentText)
                            .setLargeIcon(largeIcon).setNumber(++notificationCount)
                            .setSound(Settings.System.DEFAULT_NOTIFICATION_URI).setAutoCancel(true);

                    // Make something interesting happen when the user clicks on
                    // the notification. In this case, opening the app is sufficient.
                    Intent resultIntent = new Intent(context, MainActivity.class);

                    // The stack builder object will contain an artificial back stack for the started Activity.
                    // This ensures that navigating backward from the Activity leads out of your application to the Home screen.
                    TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
                    stackBuilder.addNextIntent(resultIntent);
                    PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
                            PendingIntent.FLAG_UPDATE_CURRENT);
                    mBuilder.setContentIntent(resultPendingIntent);

                    NotificationManager mNotificationManager = (NotificationManager) getContext()
                            .getSystemService(Context.NOTIFICATION_SERVICE);
                    // TWEET_NOTIFICATION_ID allows you to update the
                    // notification later on.
                    mNotificationManager.notify(TWEET_NOTIFICATION_ID, mBuilder.build());

                    // refreshing last sync
                    SharedPreferences.Editor editor = prefs.edit();
                    editor.putLong(lastNotificationKey, System.currentTimeMillis());
                    editor.commit();
                }

            }
        }

    }

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({ HASHTAG_STATUS_OK, HASHTAG_SERVER_DOWN, HASHTAG_STATUS_SERVER_INVALID, HASHTAG_STATUS_UNKNOWN })
    public @interface HashTagStatus {
    }

}