/**
* Copyright (C) 2013 Johannes Schnatterer
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This file is part of nusic.
*
* nusic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nusic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nusic. If not, see <http://www.gnu.org/licenses/>.
*/
package info.schnatterer.nusic.android;
import info.schnatterer.nusic.android.service.LoadNewReleasesService;
import info.schnatterer.nusic.android.service.LoadNewReleasesServiceConnection;
import info.schnatterer.nusic.android.util.Toast;
import info.schnatterer.nusic.core.ServiceException;
import info.schnatterer.nusic.core.SyncReleasesService;
import info.schnatterer.nusic.core.event.ArtistProgressListener;
import info.schnatterer.nusic.data.model.Artist;
import info.schnatterer.nusic.ui.R;
import java.util.LinkedList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
/**
* Holds the binding to the {@link LoadNewReleasesService} via
* {@link LoadNewReleasesServiceConnection}. Allows for executing the service
* method {@link SyncReleasesService#refreshReleases(boolean)} and visualizes
* its result in a {@link ProgressDialog}.
*
* @author schnatterer
*
*/
public class LoadNewRelasesServiceBinding {
private static final Logger LOG = LoggerFactory
.getLogger(LoadNewRelasesServiceBinding.class);
/**
* Context in which the {@link #progressDialog} is displayed
*/
private Activity activity;
private ProgressDialog progressDialog = null;
private List<Artist> errorArtists;
private int totalArtists = 0;
private LoadNewReleasesServiceConnection loadNewReleasesServiceConnection = null;
private ProgressListener artistProcessedListener = new ProgressListener();
private boolean isDataChanged = false;
/**
* Executes {@link SyncReleasesService#syncReleases()} within
* {@link LoadNewReleasesService} in a separate thread.
*
* @param activity
* activity that is used to display the {@link ProgressDialog}
* @param updateOnlyIfNecessary
* launch the service but do the update only
* @return <code>true</code> if refresh was started. <code>false</code> if
* already in progress.
*/
// TODO get rid of updateOnlyIfNecessary
public boolean refreshReleases(Activity activity,
boolean updateOnlyIfNecessary) {
// Make sure the progress dialog is bound to any new activty
updateActivity(activity);
if (loadNewReleasesServiceConnection != null
&& loadNewReleasesServiceConnection.isBound()) {
/*
* Execute the service method, if not running already. Pass/update
* listener.
*/
LOG.debug("Service already bound. Calling refreshReleases()");
return loadNewReleasesServiceConnection.getLoadNewReleasesService()
.refreshReleases(updateOnlyIfNecessary,
artistProcessedListener);
} else {
// Start service and bind to it
LOG.debug("Service not bound. Binding");
loadNewReleasesServiceConnection = startAndBindService(activity,
updateOnlyIfNecessary);
return true;
}
}
public boolean isRunning() {
if (loadNewReleasesServiceConnection != null
&& loadNewReleasesServiceConnection.isBound()) {
return loadNewReleasesServiceConnection.getLoadNewReleasesService()
.isRunning();
} else {
LOG.warn("Service unexpectedly not bound, assuming it's not running");
return false;
}
}
/**
* Start {@link LoadNewReleasesService}, then binds to it. In doing so, the
* service can hopefully linger on after the unbinding (if it still is
* running).
*
* @param packageContext
* some context start the service from
* @param updateOnlyIfNeccesary
* @return
*/
private LoadNewReleasesServiceConnection startAndBindService(
Context packageContext, Boolean updateOnlyIfNeccesary) {
boolean startRightAway = false;
boolean updateOnlyIfNeccesaryPrimitive = true;
if (updateOnlyIfNeccesary != null) {
startRightAway = true;
updateOnlyIfNeccesaryPrimitive = updateOnlyIfNeccesary;
}
errorArtists = new LinkedList<Artist>();
totalArtists = 0;
return LoadNewReleasesServiceConnection.startAndBind(packageContext,
startRightAway, artistProcessedListener,
updateOnlyIfNeccesaryPrimitive);
}
/**
* This should be called whenever when the application is
* paused/destroyed/stopped by the system. Don't forget to call
* {@link #bindService()}.
*/
public void unbindService() {
if (loadNewReleasesServiceConnection != null) {
LOG.debug("Unbinding service");
loadNewReleasesServiceConnection.unbind();
loadNewReleasesServiceConnection = null;
}
}
/**
* Sets a new {@link Activity} for the {@link ProgressDialog}.
*
* @param newActivity
* can be <code>null</code>
*/
public void updateActivity(Activity newActivity) {
if (this.activity != newActivity) {
this.activity = newActivity;
hideProgressDialog();
}
}
/**
* Shows the context dialog, if there is any progress going on. Useful if
* the dialog might have been hidden.
*/
public void showDialog() {
if (progressDialog != null) {
progressDialog.show();
}
}
public void hideProgressDialog() {
if (progressDialog != null) {
progressDialog.dismiss();
progressDialog = null;
}
}
/**
* Set the progress of the {@link ProgressDialog}.
*
* @param progress
* @param max
*/
private void setProgress(int progress, int max) {
if (progressDialog == null) {
// Try to show the dialog is shown
progressDialog = showDialog(progress, max);
}
if (progressDialog != null) {
progressDialog.setProgress(progress);
}
}
/**
* This should only be called from from the main thread (e.g. from
* {@link #onProgressUpdate(Object...)}).
*
* @param progress
* @param max
* @return
*/
private ProgressDialog showDialog(int progress, int max) {
ProgressDialog dialog = null;
if (activity != null) {
dialog = new ProgressDialog(activity);
dialog.setMessage(activity
.getString(R.string.LoadNewReleasesBinding_CheckingArtists));
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMax(max);
dialog.setProgress(progress);
dialog.show();
}
return dialog;
}
/**
* Handles updating the {@link ProgressDialog}.
*
* @author schnatterer
*
*/
private class ProgressListener implements ArtistProgressListener {
@Override
public void onProgressStarted(final int nEntities) {
totalArtists = nEntities;
runOnUiThread(new Runnable() {
@Override
public void run() {
progressDialog = showDialog(0, nEntities);
}
});
}
@Override
public void onProgress(final Artist entity, final int progress,
final int max, Throwable potentialException) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (entity != null) {
setProgress(progress, max);
}
}
});
if (potentialException != null) {
errorArtists.add(entity);
}
}
@Override
public void onProgressFinished(Boolean result) {
notifyListeners(result);
runOnUiThread(new Runnable() {
@Override
public void run() {
hideProgressDialog();
if (errorArtists != null && errorArtists.size() > 0) {
Toast.toast(
activity,
R.string.LoadNewReleasesBinding_finishedWithErrors,
errorArtists.size(), totalArtists);
}
}
});
}
@Override
public void onProgressFailed(Artist entity, int progress, int max,
Boolean result, final Throwable potentialException) {
notifyListeners(result);
runOnUiThread(new Runnable() {
@Override
public void run() {
hideProgressDialog();
if (potentialException != null) {
if (potentialException instanceof ServiceException) {
Toast.toast(
activity,
activity.getString(R.string.LoadNewReleasesBinding_errorFindingReleases)
+ potentialException
.getLocalizedMessage());
} else {
Toast.toast(
activity,
activity.getString(R.string.LoadNewReleasesBinding_errorFindingReleasesGeneric)
+ potentialException.getClass()
.getSimpleName());
}
}
}
});
}
private void runOnUiThread(Runnable runnable) {
if (activity != null) {
activity.runOnUiThread(runnable);
}
}
}
/**
* Checks if the service has finished and changed data since the last call
* of this method. If so returns <code>true</code> and resets the variable.
* So all subsequent calls to the method will return <code>false</code>
* until the next change of data.
*
* This is somewhat opposed to the listener mechanism, however this can be
* useful to check if the service has been active while the activity was
* paused.
*
* @return
*/
public boolean checkDataChanged() {
if (isDataChanged) {
isDataChanged = false;
return true;
}
return false;
}
protected void notifyListeners(Boolean resultChanged) {
boolean primitiveResult = true;
LOG.debug("Service: Notifying activity if result changed. ResultChanged="
+ resultChanged + ". Activity=" + activity);
// Be defensive: Only if explicitly nothing changed
if (resultChanged != null && resultChanged.equals(false)) {
primitiveResult = false;
}
isDataChanged = primitiveResult;
if (isDataChanged && activity != null) {
activity.onContentChanged();
}
}
}