package org.voxe.android.data;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.System.currentTimeMillis;
import static org.voxe.android.common.LogHelper.log;
import static org.voxe.android.common.LogHelper.logDuration;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.voxe.android.R;
import org.voxe.android.VoxeApplication;
import org.voxe.android.common.BitmapHelper;
import org.voxe.android.common.LogHelper;
import org.voxe.android.model.Candidate;
import org.voxe.android.model.Election;
import org.voxe.android.model.ElectionsHolder;
import org.voxe.android.model.Icon;
import org.voxe.android.model.Photo;
import org.voxe.android.model.PhotoSizeInfo;
import org.voxe.android.model.Tag;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import com.google.common.base.Optional;
import com.ubikod.capptain.android.sdk.CapptainAgent;
/**
*
* @param <T>
* the activity should call {@link #bindActivity(Activity)} in it's
* onResume() method and {@link #unbindActivity()} in its onPause()
* method.
*/
public class ElectionDownloadTask<T extends Activity & DownloadListener> extends AsyncTask<Void, DownloadProgress, TaskResult<ElectionsHolder>> {
private Optional<T> optionalActivity;
private TaskResult<ElectionsHolder> result;
private DownloadProgress currentProgress = DownloadProgress.create(0, 0, "");
private final VoxeApplication application;
/**
* @param activity
* cannot be null
*/
public ElectionDownloadTask(T activity, VoxeApplication application) {
bindActivity(activity);
this.application = application;
}
@Override
protected TaskResult<ElectionsHolder> doInBackground(Void... params) {
try {
publishProgress(DownloadProgress.create(0, 20, application.getString(R.string.downloading_election_data)));
log("Starting download & update of election in background");
long start = currentTimeMillis();
ElectionResourceClient electionClient = buildElectionResourceClient();
long downloadStart = currentTimeMillis();
ElectionResponse response = electionClient.getElections();
logDuration("Election download in background", downloadStart);
if (!response.meta.isOk()) {
throw new RuntimeException("Response code not OK: " + response.meta.code);
}
ElectionsHolder electionHolder = response.response;
ElectionDAO electionDAO = new ElectionDAO(application);
List<Candidate> mainCandidates = new ArrayList<Candidate>();
for (Election election : electionHolder.elections) {
mainCandidates.addAll(election.getMainCandidates());
}
{
publishProgress(DownloadProgress.create(20, 50, application.getString(R.string.downloading_candidate_photos)));
long startDownloadingCandidatePhotos = currentTimeMillis();
Map<String, Photo> loadedPhotos = new HashMap<String, Photo>();
float progressPerDownload = 30f / mainCandidates.size();
int i = 0;
for (Candidate candidate : mainCandidates) {
Photo photo = candidate.photo;
if (electionDAO.shouldDownloadCandidatePhoto(photo)) {
String uniqueId = photo.sizes.getLargestSize().get().getUniqueId();
if (loadedPhotos.containsKey(uniqueId)) {
Photo cachedPhoto = loadedPhotos.get(uniqueId);
candidate.photo = cachedPhoto;
} else {
Optional<PhotoSizeInfo> largestSize = candidate.photo.sizes.getLargestSize();
if (largestSize.isPresent()) {
String urlString = largestSize.get().url;
URL url;
try {
LogHelper.log("Loading photo: " + urlString + " for canditate " + candidate.getName());
url = new URL(urlString);
URLConnection openConnection = url.openConnection();
InputStream inputStream = openConnection.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
candidate.photo.photoBitmap = bitmap;
electionDAO.saveCandidatePhoto(photo);
loadedPhotos.put(uniqueId, photo);
} catch (IOException e) {
LogHelper.logException("Could not download candidate photo at url " + urlString, e);
}
}
}
}
i++;
publishProgress(DownloadProgress.create(20 + (int) (i * progressPerDownload), 50));
}
LogHelper.logDuration("Downloaded candidate photos", startDownloadingCandidatePhotos);
}
{
publishProgress(DownloadProgress.create(50, 80, application.getString(R.string.downloading_tag_images)));
long startDownloadingTagPhotos = currentTimeMillis();
List<Tag> tags = new ArrayList<Tag>();
for (Election election : electionHolder.elections) {
tags.addAll(election.tags);
}
Map<String, Icon> loadedIcons = new HashMap<String, Icon>();
float progressPerDownload = 30f / tags.size();
int i = 0;
for (Tag tag : tags) {
Icon icon = tag.icon;
if (electionDAO.shouldDownloadTagPhoto(icon)) {
String uniqueId = icon.getUniqueId().get();
if (loadedIcons.containsKey(uniqueId)) {
Icon cachedIcon = loadedIcons.get(uniqueId);
tag.icon = cachedIcon;
} else {
Optional<String> largestIconUrl = icon.getLargestIconUrl();
if (largestIconUrl.isPresent()) {
String urlString = largestIconUrl.get();
URL url;
try {
LogHelper.log("Loading icon: " + urlString + " for tag " + tag.name);
url = new URL(urlString);
URLConnection openConnection = url.openConnection();
InputStream inputStream = openConnection.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
icon.bitmap = bitmap;
electionDAO.saveTagImage(icon);
loadedIcons.put(uniqueId, icon);
} catch (IOException e) {
LogHelper.logException("Could not download tag image at url " + urlString, e);
}
}
}
}
i++;
publishProgress(DownloadProgress.create(50 + (int) (i * progressPerDownload), 80));
}
LogHelper.logDuration("Downloaded tag photos", startDownloadingTagPhotos);
}
publishProgress(DownloadProgress.create(80, 100, application.getString(R.string.preparing_and_saving_data)));
for (Election election : electionHolder.elections) {
Collections.sort(election.tags);
}
LogHelper.logDuration("Whole download in background", start);
Bundle bundle = new Bundle();
bundle.putLong("duration", currentTimeMillis() - start);
CapptainAgent.getInstance(application).sendEvent("data_update", bundle);
electionHolder.lastUpdateTimestamp = currentTimeMillis();
electionDAO.save(electionHolder);
for (Candidate candidate : mainCandidates) {
if (candidate.photo.photoBitmap != null) {
candidate.photo.photoBitmap = BitmapHelper.getRoundedCornerBitmap(candidate.photo.photoBitmap);
}
}
publishProgress(DownloadProgress.create(100, 100, application.getString(R.string.election_updated)));
return TaskResult.fromResult(electionHolder);
} catch (Exception e) {
LogHelper.logException("Could not download and update the election data", e);
return TaskResult.fromException(e);
}
}
private ElectionResourceClient buildElectionResourceClient() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationConfig.Feature.AUTO_DETECT_SETTERS, false);
objectMapper.configure(DeserializationConfig.Feature.USE_GETTERS_AS_SETTERS, false);
objectMapper.configure(SerializationConfig.Feature.AUTO_DETECT_GETTERS, false);
RestTemplate restTemplate = new RestTemplate();
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
for (HttpMessageConverter<?> httpMessageConverter : messageConverters) {
if (httpMessageConverter instanceof MappingJacksonHttpMessageConverter) {
MappingJacksonHttpMessageConverter jacksonConverter = (MappingJacksonHttpMessageConverter) httpMessageConverter;
jacksonConverter.setObjectMapper(objectMapper);
}
}
ElectionResourceClient restClient = new ElectionResourceClient_();
restClient.setRestTemplate(restTemplate);
return restClient;
}
@Override
protected void onCancelled() {
unbindActivity();
}
/**
* Should be called when the activity is paused.
*
* Must be called from the UI thread
*/
public void unbindActivity() {
optionalActivity = Optional.absent();
}
/**
* Should be called when the activity is resumed.
*
* Must be called from the UI thread
*
* @param activity
* cannot be null
*/
public void bindActivity(T activity) {
optionalActivity = Optional.of(activity);
if (result != null) {
electionDownloaded();
}
}
/**
* Should be called to cancel the task, for example when the activity is
* destroyed
*/
public void destroy() {
cancel(true);
optionalActivity = Optional.absent();
}
public DownloadProgress getCurrentProgress() {
return currentProgress;
}
@Override
protected void onProgressUpdate(DownloadProgress... values) {
DownloadProgress newProgress = values[0];
if (newProgress.progressMessage == null) {
newProgress = DownloadProgress.create(newProgress.currentProgress, newProgress.nextProgress, currentProgress.progressMessage);
}
currentProgress = newProgress;
if (optionalActivity.isPresent()) {
optionalActivity.get().onDownloadProgress(currentProgress);
}
}
@Override
protected void onPostExecute(TaskResult<ElectionsHolder> result) {
if (isCancelled()) {
return;
}
this.result = result;
if (optionalActivity.isPresent()) {
electionDownloaded();
}
}
/**
* Must be called from the UI thread.
*/
private void electionDownloaded() {
checkNotNull(result);
checkState(optionalActivity.isPresent());
DownloadListener activity = optionalActivity.get();
if (result.isException()) {
activity.onDownloadError();
} else {
ElectionsHolder electionHolder = result.asResult();
application.setElectionHolder(electionHolder);
activity.onElectionDownloaded();
}
result = null;
}
}