package com.thebluealliance.androidclient.datafeed.status; import com.google.gson.Gson; import com.thebluealliance.androidclient.BuildConfig; import com.thebluealliance.androidclient.R; import com.thebluealliance.androidclient.TbaLogger; import com.thebluealliance.androidclient.Utilities; import com.thebluealliance.androidclient.accounts.AccountController; import com.thebluealliance.androidclient.activities.UpdateRequiredActivity; import com.thebluealliance.androidclient.background.AnalyticsActions; import com.thebluealliance.androidclient.models.ApiStatus; import android.app.Activity; import android.app.Application; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AlertDialog; import java.io.IOException; import java.util.Calendar; import java.util.Date; import javax.inject.Inject; import javax.inject.Singleton; import okhttp3.Cache; import rx.schedulers.Schedulers; /** * A class to handle the TBA Status API * Can fire off {@link StatusRefreshService} to load new data */ @Singleton public class TBAStatusController implements Application.ActivityLifecycleCallbacks { public static final String STATUS_PREF_KEY = "tba_status"; private static final String STATUS_CACHE_CLEAR_KEY = "last_okcache_clear"; /** * We use different timeouts for logged in users and not logged in users. Logged-in users will * receive push notifications when the status changes, so it's not as important to poll * frequently. For logged-out users, however, we want to poll more often because they won't * get push notifications. */ // 15 minutes private static final double UPDATE_TIMEOUT_LOGGED_IN_NS = 9e+11; // 1 minute private static final double UPDATE_TIMEOUT_LOGGED_OUT_NS = 6e+10; // 3 hours private static final double DIALOG_TIMEOUT = 1.08e+13; private final SharedPreferences mPrefs; private final Gson mGson; private final Cache mOkHttpCache; private long mLastUpdateTime; private long mLastDialogTime; private long mLastMessageTime; private boolean mUserIsLoggedIn; @Inject public TBAStatusController(SharedPreferences prefs, Gson gson, Cache cache, AccountController accountController) { mPrefs = prefs; mGson = gson; mOkHttpCache = cache; mLastUpdateTime = Long.MIN_VALUE; mLastDialogTime = Long.MIN_VALUE; mUserIsLoggedIn = accountController.isMyTbaEnabled(); } public void scheduleStatusUpdate(Context context) { context.startService(new Intent(context, StatusRefreshService.class)); } public @Nullable ApiStatus fetchApiStatus() { if (!mPrefs.contains(STATUS_PREF_KEY)) { return null; } String statusJson = mPrefs.getString(STATUS_PREF_KEY, ""); return mGson.fromJson(statusJson, ApiStatus.class); } public int getMaxCompYear() { ApiStatus status = fetchApiStatus(); if (status == null || status.getMaxSeason() == null) { Calendar cal = Calendar.getInstance(); return cal.get(Calendar.YEAR); } return status.getMaxSeason(); } public Integer getCurrentCompYear() { ApiStatus status = fetchApiStatus(); if (status == null) { Calendar cal = Calendar.getInstance(); return cal.get(Calendar.YEAR); } return status.getCurrentSeason(); } public int getMinAppVersion() { ApiStatus status = fetchApiStatus(); if (status == null || status.getMinAppVersion() == null) { /* Default to the current version */ return BuildConfig.VERSION_CODE; } return status.getMinAppVersion(); } private int getLatestAppVersion() { ApiStatus status = fetchApiStatus(); if (status == null || status.getLatestAppVersion() == null) { /* Default to the current version */ return BuildConfig.VERSION_CODE; } return status.getLatestAppVersion(); } public void clearOkCacheIfNeeded(@Nullable ApiStatus status, boolean forceClear) { long lastCacheClear = mPrefs.getLong(STATUS_CACHE_CLEAR_KEY, Long.MIN_VALUE); if (status != null && mOkHttpCache != null && (forceClear || lastCacheClear < status.getLastOkHttpCacheClear())) { Schedulers.io().createWorker().schedule(() -> { TbaLogger.i("Clearing OkHttp cache"); try { mOkHttpCache.evictAll(); mPrefs.edit() .putLong(STATUS_CACHE_CLEAR_KEY, System.currentTimeMillis() / 1000L) .apply(); } catch (IOException e) { e.printStackTrace(); } }); } } @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { } @Override public void onActivityStarted(Activity activity) { new AnalyticsActions.ReportActivityStart(activity).run(); } @Override public void onActivityResumed(Activity activity) { /* Update myTBA Status */ double timeout = mUserIsLoggedIn ? UPDATE_TIMEOUT_LOGGED_IN_NS : UPDATE_TIMEOUT_LOGGED_OUT_NS; if (mLastUpdateTime + timeout < System.nanoTime()) { scheduleStatusUpdate(activity); mLastUpdateTime = System.nanoTime(); } /* App updates required/recommended */ if (!Utilities.isDebuggable() && BuildConfig.VERSION_CODE < getMinAppVersion()) { activity.startActivity(new Intent(activity, UpdateRequiredActivity.class)); } else if (!Utilities.isDebuggable() && BuildConfig.VERSION_CODE < getLatestAppVersion() && mLastDialogTime + DIALOG_TIMEOUT < System.nanoTime()) { /* Show an app update dialog */ new AlertDialog.Builder(activity) .setTitle(R.string.update_dialog_title) .setMessage(R.string.update_dialog_text) .setPositiveButton(R.string.update_dialog_action, (dialog, which) -> { /* Open Play Store page */ dialog.dismiss(); Intent i = new Intent(Intent.ACTION_VIEW); i.setData(Uri.parse("https://play.google.com/store/apps/details?id=com.thebluealliance.androidclient")); activity.startActivity(i); }) .setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()) .show(); mLastDialogTime = System.nanoTime(); } /* Admin push message */ ApiStatus status = fetchApiStatus(); Date now = new Date(); if (status != null && status.getHasMessage() && mLastMessageTime + DIALOG_TIMEOUT < System.nanoTime() && status.getMessageExpiration().compareTo(now.getTime()) > 0) { new AlertDialog.Builder(activity) .setMessage(status.getMessageText()) .setCancelable(false) .setPositiveButton(R.string.ok, ((dialog, which) -> dialog.dismiss())) .show(); mLastMessageTime = System.nanoTime(); } /* Clear OkHttp Cache when commanded */ clearOkCacheIfNeeded(status, false); } @Override public void onActivityPaused(Activity activity) { } @Override public void onActivityStopped(Activity activity) { new AnalyticsActions.ReportActivityStop(activity).run(); } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } @Override public void onActivityDestroyed(Activity activity) { } }