Java tutorial
/* * Copyright 2014 Nicholas Liu * * 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 ca.zadrox.dota2esportticker.service; import android.app.AlarmManager; import android.app.IntentService; import android.app.PendingIntent; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import ca.zadrox.dota2esportticker.data.MatchContract; import ca.zadrox.dota2esportticker.data.http.match.model.BundledMatchItem; import ca.zadrox.dota2esportticker.util.LogUtils; import ca.zadrox.dota2esportticker.util.MatchGetter; import ca.zadrox.dota2esportticker.util.PrefUtils; import ca.zadrox.dota2esportticker.util.TimeUtils; /** * Created by Acco on 11/13/2014. */ public class UpdateMatchService extends IntentService { private static final String TAG = UpdateMatchService.class.getSimpleName(); public static final String ACTION_UPDATE_MATCHES = "ca.zadrox.dota2esportticker.action.ACTION_UPDATE_MATCHES"; public static final String ACTION_AUTO_UPDATE_MATCHES = "ca.zadrox.dota2esportticker.action.ACTION_AUTO_UPDATE_MATCHES"; public static final String ACTION_UPDATE_RESULTS = "ca.zadrox.dota2esportticker.action.ACTION_UPDATE_RESULTS"; public static final String ACTION_SCHEDULE_AUTO_UPDATE = "ca.zadrox.dota2esportticker.action.ACTION_SCHEDULE_AUTO_UPDATE"; public static final String ACTION_CANCEL_AUTO_UPDATE = "ca.zadrox.dota2esportticker.action.ACTION_CANCEL_AUTO_UPDATE"; public static final String UPDATE_COMPLETE = "ca.zadrox.dota2esportticker.action.UPDATE_COMPLETE"; public static final String UPDATE_INTERRUPTED = "ca.zadrox.dota2esportticker.action.UPDATE_INTERRUPTED"; public static final String UPDATE_NO_CONNECTIVITY = "ca.zadrox.dota2esportticker.action.UPDATE_NO_CONNECTIVITY"; public static final String UPDATE_STARTED = "ca.zadrox.dota2esportticker.action.UPDATE_STARTED"; public UpdateMatchService() { super(TAG); } @Override protected void onHandleIntent(Intent intent) { final String action = intent.getAction(); if (ACTION_UPDATE_MATCHES.equals(action)) { updateMatches(true); } if (ACTION_AUTO_UPDATE_MATCHES.equals(action)) { autoUpdateMatches(); } if (ACTION_SCHEDULE_AUTO_UPDATE.equals(action)) { scheduleAutoUpdates(); } if (ACTION_CANCEL_AUTO_UPDATE.equals(action)) { cancelAutoUpdate(); } if (ACTION_UPDATE_RESULTS.equals(action)) { updateResults(); } } private void updateMatches(boolean doResults) { if (!checkForConnectivity()) { LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(UPDATE_NO_CONNECTIVITY)); return; } LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(UPDATE_STARTED)); final String BASE_URL = "http://www.gosugamers.net/dota2/gosubet"; final String MATCH_LINK_URL_BASE = "http://www.gosugamers.net"; try { String rawHtml = new OkHttpClient().newCall(new Request.Builder().url(BASE_URL).build()).execute() .body().string(); rawHtml = rawHtml.substring(rawHtml.indexOf("<div id=\"col1\" class=\"rows\">"), rawHtml.indexOf("<div id=\"col2\" class=\"rows\">")); Document doc = Jsoup.parse(rawHtml); Elements tables = doc.getElementsByClass("matches"); ArrayList<ArrayList<String>> matchLinks = new ArrayList<ArrayList<String>>(tables.size()); int numSeries = 0; for (Element table : tables) { Elements links = table.getElementsByClass("match"); if (links.size() != 0) { ArrayList<String> innerMatchLink = new ArrayList<String>(links.size()); for (Element link : links) { String linkHref = link.attr("href"); innerMatchLink.add(MATCH_LINK_URL_BASE + linkHref); numSeries++; } matchLinks.add(innerMatchLink); } } // needed if there are massive reschedules to update content properly. Uri resultsUri = MatchContract.SeriesEntry.buildSeriesUriWithAfterTime(TimeUtils.getUTCTime()); Cursor c = getContentResolver().query(resultsUri, new String[] { MatchContract.SeriesEntry.COLUMN_GG_MATCH_PAGE }, null, null, null); while (c.moveToNext()) { if (!matchLinks.get(0).contains(c.getString(0))) { matchLinks.get(0).add(c.getString(0)); } } Iterator<ArrayList<String>> iterator = matchLinks.iterator(); int numResults = 0; ExecutorService executorService = Executors.newFixedThreadPool(10); ArrayList<Future<BundledMatchItem>> seriesItemFutures = new ArrayList<Future<BundledMatchItem>>( numSeries); LogUtils.LOGD(TAG, "Starting Retrieval, num elements gathered: " + numSeries); int i = 0; while (iterator.hasNext()) { ArrayList<String> matchList = iterator.next(); for (String matchUrl : matchList) { boolean hasResult = !iterator.hasNext(); if (!doResults && hasResult) { continue; } else if (hasResult) { numResults++; } seriesItemFutures.add(executorService.submit(new MatchGetter(matchUrl, hasResult))); i++; } } executorService.shutdown(); executorService.awaitTermination(20L, TimeUnit.SECONDS); LogUtils.LOGD(TAG, "Stopping Retrieval, elements submitted for fetching: " + i); ContentValues[] seriesEntries = new ContentValues[i]; ContentValues[] resultEntries = new ContentValues[numResults]; int seriesEntryWriteIndex = 0; int resultEntryWriteIndex = 0; for (Future<BundledMatchItem> seriesItemFuture : seriesItemFutures) { try { BundledMatchItem seriesItem = seriesItemFuture.get(); if (seriesItem != null) { seriesEntries[seriesEntryWriteIndex] = seriesItem.mMatch; seriesEntryWriteIndex++; if (seriesItem.hasResult) { resultEntries[resultEntryWriteIndex] = seriesItem.mResult; resultEntryWriteIndex++; } } } catch (ExecutionException e) { Log.e(TAG, "Should never get here"); } } this.getContentResolver().bulkInsert(MatchContract.SeriesEntry.CONTENT_URI, seriesEntries); if (doResults) this.getContentResolver().bulkInsert(MatchContract.ResultEntry.CONTENT_URI, resultEntries); PrefUtils.setLastUpdateTime(this, TimeUtils.getUTCTime()); } catch (IOException e) { Log.e(TAG, e.getMessage(), e); e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(UPDATE_COMPLETE)); PrefUtils.setLastResultsUpdateTime(this, TimeUtils.getUTCTime()); } private void autoUpdateMatches() { long currentTime = TimeUtils.getUTCTime(); long lastUpdateTime = PrefUtils.lastMatchUpdate(this); // no point in consuming so much data - skip update if last checked time is recent. if (currentTime - lastUpdateTime < 60000 * 60 * 1) { return; } if (wifiUpdate()) { updateMatches(false); } } private void scheduleAutoUpdates() { if (!PrefUtils.shouldAutoUpdate(this)) { return; } final long currentTime = TimeUtils.getUTCTime(); final Intent updateMatchIntent = new Intent(UpdateMatchService.ACTION_AUTO_UPDATE_MATCHES, null, this, UpdateMatchService.class); final Intent updateResultIntent = new Intent(UpdateMatchService.ACTION_UPDATE_RESULTS, null, this, UpdateMatchService.class); final long matchUpdateTime = currentTime + (60000 * 60 * 12); final long resultUpdateTime = currentTime + (60000 * 60); PendingIntent umi = PendingIntent.getService(this.getApplicationContext(), 0, updateMatchIntent, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent uri = PendingIntent.getService(this.getApplicationContext(), 0, updateResultIntent, PendingIntent.FLAG_UPDATE_CURRENT); final AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); am.cancel(umi); am.cancel(uri); am.setInexactRepeating(AlarmManager.RTC, matchUpdateTime, AlarmManager.INTERVAL_HALF_DAY, umi); am.setInexactRepeating(AlarmManager.RTC, resultUpdateTime, AlarmManager.INTERVAL_HOUR, uri); } private void cancelAutoUpdate() { final Intent updateMatchIntent = new Intent(UpdateMatchService.ACTION_AUTO_UPDATE_MATCHES, null, this, UpdateMatchService.class); final Intent updateResultIntent = new Intent(UpdateMatchService.ACTION_UPDATE_RESULTS, null, this, UpdateMatchService.class); PendingIntent umi = PendingIntent.getService(this.getApplicationContext(), 0, updateMatchIntent, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent uri = PendingIntent.getService(this.getApplicationContext(), 0, updateResultIntent, PendingIntent.FLAG_UPDATE_CURRENT); final AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); am.cancel(umi); am.cancel(uri); } // we decouple results and matches because results tends to require less data // check this every hour, with no cpu wake? private void updateResults() { long currentTime = TimeUtils.getUTCTime(); long lastUpdateTime = PrefUtils.lastResultsUpdate(this); // no point in consuming so much data - skip update if last checked time is recent. if (currentTime - lastUpdateTime < 60000 * 10) { LogUtils.LOGD(TAG, "Too soon, not bothering to check."); return; } if (!checkForConnectivity()) { LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(UPDATE_NO_CONNECTIVITY)); return; } LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(UPDATE_STARTED)); Uri resultsUri = MatchContract.SeriesEntry.buildLiveSeriesUri(TimeUtils.getUTCTime()); Cursor updateCursor = this.getContentResolver().query(resultsUri, new String[] { MatchContract.SeriesEntry.COLUMN_GG_MATCH_PAGE }, null, null, null); // LOGD(TAG, "Not executed"); ExecutorService executorService = Executors.newFixedThreadPool(10); ArrayList<Future<BundledMatchItem>> matchItemFutures = new ArrayList<Future<BundledMatchItem>>(); int i = 0; while (updateCursor.moveToNext()) { matchItemFutures.add(executorService.submit(new MatchGetter(updateCursor.getString(0), true))); i++; } updateCursor.close(); executorService.shutdown(); try { executorService.awaitTermination(20L, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } LogUtils.LOGD(TAG, "Stopping Retrieval, elements submitted for fetching: " + i); ArrayList<ContentValues> sE = new ArrayList<ContentValues>(); ArrayList<ContentValues> rE = new ArrayList<ContentValues>(); for (Future<BundledMatchItem> matchItemFuture : matchItemFutures) { try { BundledMatchItem matchItem = matchItemFuture.get(); if (matchItem != null) { sE.add(matchItem.mMatch); if (matchItem.hasResult) { rE.add(matchItem.mResult); } } } catch (InterruptedException e) { Log.e(TAG, "Should never get here"); } catch (ExecutionException e) { Log.e(TAG, "Oops;"); } } if (!sE.isEmpty()) { ContentValues[] seriesEntries = new ContentValues[sE.size()]; sE.toArray(seriesEntries); this.getContentResolver().bulkInsert(MatchContract.SeriesEntry.CONTENT_URI, seriesEntries); } if (!rE.isEmpty()) { ContentValues[] resultEntries = new ContentValues[rE.size()]; resultEntries = rE.toArray(resultEntries); this.getContentResolver().bulkInsert(MatchContract.ResultEntry.CONTENT_URI, resultEntries); } LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(UPDATE_COMPLETE)); PrefUtils.setLastResultsUpdateTime(this, currentTime); } private boolean checkForConnectivity() { ConnectivityManager cm = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); return activeNetwork != null && activeNetwork.isConnectedOrConnecting(); } private boolean wifiUpdate() { ConnectivityManager cm = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); boolean wifiState = true; if (PrefUtils.updateOnWifiOnly(this)) { wifiState = activeNetwork.getType() == ConnectivityManager.TYPE_WIFI; } return wifiState; } }