com.meiste.greg.ptw.WidgetProvider.java Source code

Java tutorial

Introduction

Here is the source code for com.meiste.greg.ptw.WidgetProvider.java

Source

/*
 * Copyright (C) 2013-2014 Gregory S. Meiste  <http://gregmeiste.com>
 *
 * 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.meiste.greg.ptw;

import java.io.InputStream;
import java.net.URL;
import java.util.Calendar;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.client.DefaultHttpClient;

import uk.co.senab.bitmapcache.BitmapLruCache;
import uk.co.senab.bitmapcache.CacheableBitmapDrawable;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.text.format.DateUtils;
import android.widget.RemoteViews;

import com.google.android.gms.tagmanager.Container;
import com.meiste.greg.ptw.GtmHelper.OnContainerAvailableListener;
import com.meiste.greg.ptw.tab.Questions;

public class WidgetProvider extends AppWidgetProvider {

    private static final long UPDATE_INTERVAL = DateUtils.MINUTE_IN_MILLIS;
    private static final long UPDATE_FUDGE = 75; /* milliseconds */
    private static final long UPDATE_WARNING = (DateUtils.HOUR_IN_MILLIS * 2) + UPDATE_FUDGE;

    private static final String WIDGET_STATE = "widget.enabled";

    private static Race sRace;

    @Override
    public void onReceive(final Context context, final Intent intent) {
        if (intent.hasExtra(Intent.EXTRA_ALARM_COUNT)) {
            Util.log("WidgetProvider.onReceive: Widget alarm");
            new UpdateWidgetThread(context).start();
            setAlarm(context);
        } else if (intent.getAction().equals(Intent.ACTION_TIME_CHANGED)) {
            Util.log("WidgetProvider.onReceive: Time change");
            setAlarm(context);
        } else if (intent.getAction().equals(PTW.INTENT_ACTION_SCHEDULE)) {
            Util.log("WidgetProvider.onReceive: Schedule Updated");

            final int[] appWidgetIds = getInstalledWidgets(context);
            if (appWidgetIds.length > 0) {
                /* Force full widget update */
                sRace = null;
                onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
            }
        } else if (intent.getAction().equals(PTW.INTENT_ACTION_ANSWERS)) {
            Util.log("WidgetProvider.onReceive: Answers submitted");
            final int[] appWidgetIds = getInstalledWidgets(context);
            if (appWidgetIds.length > 0) {
                new UpdateWidgetThread(context).start();
            }
        } else
            super.onReceive(context, intent);
    }

    @Override
    public void onEnabled(final Context context) {
        final boolean prevEnabled = Util.getState(context).getBoolean(WIDGET_STATE, false);
        Util.log("WidgetProvider.onEnabled: prevEnabled=" + prevEnabled);

        /* onEnabled gets called on device power up, so prevent extra enables
         * from being tracked. */
        if (!prevEnabled) {
            Util.getState(context).edit().putBoolean(WIDGET_STATE, true).apply();
            Analytics.trackEvent(context, "Widget", "state", "enabled");
        }
    }

    @Override
    public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) {
        Util.log("WidgetProvider.onUpdate: num=" + appWidgetIds.length);

        final RemoteViews rViews = new RemoteViews(context.getPackageName(), R.layout.widget_loading);
        appWidgetManager.updateAppWidget(appWidgetIds, rViews);

        new UpdateWidgetThread(context).start();

        /* Set alarm to update widget when device is awake. */
        setAlarm(context);
    }

    @Override
    public void onDisabled(final Context context) {
        Util.log("WidgetProvider.onDisabled");

        final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        am.cancel(getAlarmIntent(context));

        Util.getState(context).edit().putBoolean(WIDGET_STATE, false).apply();
        Analytics.trackEvent(context, "Widget", "state", "disabled");
    }

    @SuppressLint("NewApi")
    private void setAlarm(final Context context) {
        /* No point setting alarm if no widgets */
        if (getInstalledWidgets(context).length == 0)
            return;

        final long now = System.currentTimeMillis();
        long next = UPDATE_INTERVAL - (now % UPDATE_INTERVAL) - UPDATE_FUDGE;
        if (next <= 0) {
            next += UPDATE_INTERVAL;
        }

        final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            am.setExact(AlarmManager.RTC, now + next, getAlarmIntent(context));
        } else {
            am.set(AlarmManager.RTC, now + next, getAlarmIntent(context));
        }
    }

    private PendingIntent getAlarmIntent(final Context context) {
        final Intent intent = new Intent(context, WidgetProvider.class);
        return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

    private int[] getInstalledWidgets(final Context context) {
        final ComponentName thisWidget = new ComponentName(context, WidgetProvider.class);
        final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
        return appWidgetManager.getAppWidgetIds(thisWidget);
    }

    private class UpdateWidgetThread extends Thread implements OnContainerAvailableListener {
        private static final int RESULT_SUCCESS = 0;
        private static final int RESULT_FAILURE = 1;
        private static final int RESULT_NO_RACE = 2;
        private static final int RESULT_DISABLE = 3;

        private static final int PI_REQ_CODE = 810647;

        private final Context mContext;
        private CacheableBitmapDrawable mBitmapDrawable;

        private final Object mSync = new Object();
        private Container mContainer;

        public UpdateWidgetThread(final Context context) {
            super(UpdateWidgetThread.class.getSimpleName());
            mContext = context;
            GtmHelper.getInstance(context).getContainer(this);
        }

        @Override
        public void onContainerAvailable(final Container container) {
            synchronized (mSync) {
                mContainer = container;
                mSync.notify();
            }
        }

        private String getURL(final Race race, final boolean addYear) {
            final StringBuilder sb = new StringBuilder();
            sb.append(GAE.PROD_URL).append("/img/race/");

            if (addYear) {
                sb.append(Calendar.getInstance().get(Calendar.YEAR));
                sb.append('_');
            }

            sb.append(race.getId()).append(".png");

            return sb.toString();
        }

        @Override
        public void run() {
            synchronized (mSync) {
                if (mContainer == null) {
                    try {
                        mSync.wait();
                    } catch (final InterruptedException e) {
                    }
                }
            }

            if (mContainer.getBoolean(GtmHelper.KEY_GAME_ENABLED)) {
                setUi(getRaceLogo());
            } else {
                setUi(RESULT_DISABLE);
            }
        }

        private int getRaceLogo() {
            final BitmapLruCache bitmapCache = PTW.getApplication(mContext).getBitmapCache();

            // First handle recent race scenario
            if ((sRace != null) && sRace.isRecent()) {
                mBitmapDrawable = bitmapCache.get(getURL(sRace, true));
                if (mBitmapDrawable != null) {
                    Util.log("Recent race logo found in cache");
                    try {
                        /* Need to fudge the time the other way */
                        Thread.sleep(UPDATE_FUDGE * 2);
                    } catch (final InterruptedException e) {
                    }

                    if (sRace.isRecent()) {
                        return RESULT_SUCCESS;
                    }
                }
            }

            sRace = Race.getNext(mContext, true, true);

            // Second, handle end of season scenario
            if (sRace == null)
                return RESULT_NO_RACE;

            // Third, handle upcoming race already cached scenario
            final String url = getURL(sRace, true);
            mBitmapDrawable = bitmapCache.get(url);
            if (mBitmapDrawable != null) {
                Util.log("Upcoming race logo found in cache");
                return RESULT_SUCCESS;
            }

            // Finally, handle upcoming race not cached scenario
            try {
                final HttpGet httpRequest = new HttpGet(new URL(getURL(sRace, false)).toURI());
                final HttpClient httpclient = new DefaultHttpClient();
                final HttpResponse resp = httpclient.execute(httpRequest);
                final int statusCode = resp.getStatusLine().getStatusCode();

                switch (statusCode) {
                case HttpStatus.SC_OK:
                    final HttpEntity entity = resp.getEntity();
                    final BufferedHttpEntity b_entity = new BufferedHttpEntity(entity);
                    final InputStream input = b_entity.getContent();

                    mBitmapDrawable = bitmapCache.put(url, input);
                    if (mBitmapDrawable == null) {
                        Util.log("Get logo failed to decode bitmap");
                        return RESULT_FAILURE;
                    }
                    break;
                default:
                    Util.log("Get logo failed (statusCode = " + statusCode + ")");
                    return RESULT_FAILURE;
                }
            } catch (final Exception e) {
                Util.log("Get logo failed with exception " + e);
                return RESULT_FAILURE;
            }

            return RESULT_SUCCESS;
        }

        private void setUi(final int result) {
            Util.log("UpdateWidgetThread.setUi: result=" + result);
            RemoteViews rViews;

            switch (result) {
            case RESULT_SUCCESS:
                if (sRace == null) {
                    // Schedule update received while downloading logo
                    return;
                }
                final int str_id = sRace.isRecent() ? R.string.widget_current_race : R.string.widget_next_race;
                final String nextRace = mContext.getString(str_id, sRace.getStartRelative(mContext));
                rViews = new RemoteViews(mContext.getPackageName(), R.layout.widget);
                rViews.setTextViewText(R.id.when, nextRace);
                rViews.setImageViewBitmap(R.id.race_logo, mBitmapDrawable.getBitmap());

                if (sRace.isExhibition()) {
                    rViews.setInt(R.id.status, "setText", R.string.widget_exhibition);
                    rViews.setInt(R.id.widget_text_layout, "setBackgroundResource", R.drawable.widget_normal);
                } else if (sRace.isRecent()) {
                    rViews.setInt(R.id.status, "setText", R.string.widget_no_results);
                    rViews.setInt(R.id.widget_text_layout, "setBackgroundResource", R.drawable.widget_normal);
                } else if (!sRace.inProgress()) {
                    rViews.setInt(R.id.status, "setText", R.string.widget_no_questions);
                    rViews.setInt(R.id.widget_text_layout, "setBackgroundResource", R.drawable.widget_normal);
                } else {
                    final SharedPreferences acache = mContext.getSharedPreferences(Questions.ACACHE,
                            Activity.MODE_PRIVATE);
                    if (acache.contains(Questions.cachePrefix() + sRace.getId())) {
                        rViews.setInt(R.id.status, "setText", R.string.widget_submitted);
                        rViews.setInt(R.id.widget_text_layout, "setBackgroundResource", R.drawable.widget_good);
                    } else if ((sRace.getStartTimestamp() - System.currentTimeMillis()) <= UPDATE_WARNING) {
                        rViews.setInt(R.id.status, "setText", R.string.widget_no_answers);
                        rViews.setInt(R.id.widget_text_layout, "setBackgroundResource", R.drawable.widget_warning);
                    } else {
                        rViews.setInt(R.id.status, "setText", R.string.widget_please_submit);
                        rViews.setInt(R.id.widget_text_layout, "setBackgroundResource", R.drawable.widget_normal);
                    }
                }

                Intent intent = new Intent(mContext, MainActivity.class);
                intent.putExtra(PTW.INTENT_EXTRA_TAB, 1);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                PendingIntent pi = PendingIntent.getActivity(mContext, PI_REQ_CODE, intent,
                        PendingIntent.FLAG_UPDATE_CURRENT);
                rViews.setOnClickPendingIntent(R.id.widget_text_layout, pi);

                intent = new Intent(mContext, RaceActivity.class);
                intent.putExtra(RaceActivity.INTENT_ID, sRace.getId());
                intent.putExtra(RaceActivity.INTENT_ALARM, true);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                pi = PendingIntent.getActivity(mContext, PI_REQ_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
                rViews.setOnClickPendingIntent(R.id.race_logo, pi);
                break;
            case RESULT_NO_RACE:
                rViews = new RemoteViews(mContext.getPackageName(), R.layout.widget_no_race);

                intent = new Intent(mContext, MainActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                pi = PendingIntent.getActivity(mContext, PI_REQ_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
                rViews.setOnClickPendingIntent(R.id.widget_full_layout, pi);
                break;
            case RESULT_DISABLE:
                rViews = new RemoteViews(mContext.getPackageName(), R.layout.widget_disabled);
                break;
            case RESULT_FAILURE:
            default:
                rViews = new RemoteViews(mContext.getPackageName(), R.layout.widget_error);

                intent = new Intent(mContext, WidgetProvider.class);
                intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, getInstalledWidgets(mContext));
                pi = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
                rViews.setOnClickPendingIntent(R.id.widget_error_layout, pi);
                break;
            }

            final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
            appWidgetManager.updateAppWidget(getInstalledWidgets(mContext), rViews);
        }
    }
}