/** Copyright 2015 Tim Engler, Rareventure LLC This file is part of Tiny Travel Tracker. Tiny Travel Tracker 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. Tiny Travel Tracker 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 Tiny Travel Tracker. If not, see <http://www.gnu.org/licenses/>. */ package com.rareventure.android; import android.app.Dialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.util.Log; import com.rareventure.gps2.GTG; import com.rareventure.gps2.GTGFragmentActivity; /** * Allows an activity to run one or more background tasks and show a spinner dialog or progress bar */ public abstract class ProgressDialogActivity extends GTGFragmentActivity { public WTask task; protected boolean dialogShowing; protected MyProgressDialogFragment fragment; private int max; public ProgressDialogActivity () { } @Override public void doOnCreate(Bundle savedInstanceState) { super.doOnCreate(savedInstanceState); } protected void openDialogForWTask(WTask task, final boolean isCancelable, final boolean indeterminate, final int titleId, final int msgId) { this.task = task; showDialog(isCancelable, indeterminate, titleId, msgId); } private void showDialog(final boolean isCancelable, final boolean indeterminate, final int titleId, final int msgId) { dialogShowing = true; fragment = new MyProgressDialogFragment(this, isCancelable, indeterminate, titleId, msgId); fragment.show(ProgressDialogActivity.this.getSupportFragmentManager(), "dialog"); } protected void runLongTask(Task task, final boolean isCancelable, final boolean indeterminate, final int titleId, final int msgId) { this.task = task; showDialog(isCancelable, indeterminate, titleId, msgId); new Thread(task).start(); } /** * A class implementing this will run using it's own threads/callbacks * and notify this task when it's work is done. It will be notified if * canceled with cancel() */ public abstract class WTask { protected boolean isShutdown = false; public WTask() { } public void setMaxProgress(int total) { max = total; runOnUiThread(new Runnable() { @Override public void run() { fragment.dialog.setMax(max); } }); } public void updateProgress(final int start, final int end, final int progress) { runOnUiThread(new Runnable() { @Override public void run() { fragment.dialog.setMax(end - start); fragment.dialog.setProgress(progress - start); } }); } protected void setMessage(int msgId) { setMessage(getText(msgId)); } protected void setMessage(final CharSequence msg) { runOnUiThread(new Runnable() { @Override public void run() { if(fragment != null && fragment.dialog != null) fragment.dialog.setMessage(msg); } }); } /** * Should be called when the task is finished */ public void notifyFinish() { runOnUiThread(new Runnable() { @Override public void run() { if(fragment != null && fragment.dialog != null) fragment.dialog.dismiss(); dialogShowing = false; fragment = null; } }); } /** * Cancels the dialog. */ public abstract void cancel(); } /** * A class implementing this will run as a thread. */ public abstract class Task extends WTask implements Runnable { //if true, dialog is canceled and progress should quit public boolean isCanceled = false; public Task() { } /** * Should be called when the task is finished */ public void notifyFinish() { synchronized (this) { isShutdown = true; notify(); } ProgressDialogActivity.this.task = null; runOnUiThread(new Runnable() { @Override public void run() { if(fragment != null) fragment.dialog.dismiss(); dialogShowing = false; fragment = null; doAfterFinish(); } }); } /** * Cancels the dialog. */ public void cancel() { synchronized (this) { //if it was shutdown for any other reason, including being finished or canceled before, //we do not want to cancel it again if(!isShutdown) { isCanceled = true; notify(); try { //TODO 2.1 for journal rollback/forward at least, instead of waiting, we should let the process go and then reconnect with //it so we can exit immediately instead of freezing. while(!isShutdown ) wait(); } catch (InterruptedException e) { throw new IllegalStateException(e); } } } } public final void run() { doIt(); notifyFinish(); } public abstract void doIt(); public abstract void doAfterFinish(); } //this needs to be static to allow for an empty constructor public static class MyProgressDialogFragment extends DialogFragment { private boolean isCancelable; private boolean indeterminate; private int titleId; private int msgId; private ProgressDialog dialog; private ProgressDialogActivity activity; public MyProgressDialogFragment() { //TODO 2.5 I'm not sure what to do here, sometimes fragments are destroyed and recreated by the android os? //Not sure of the process or what we should do in this case because if a progress dialog fragment is destroyed // doesn't it mean the task being run was unfinished? Log.e(GTG.TAG,"no arg constructor called"); } public MyProgressDialogFragment(ProgressDialogActivity activity, boolean isCancelable, boolean indeterminate, int titleId, int msgId) { this.activity = activity; this.isCancelable = isCancelable; this.indeterminate = indeterminate; this.titleId = titleId; this.msgId = msgId; } @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { dialog = new ProgressDialog(getActivity()); dialog.setTitle(titleId); dialog.setMessage(getString(msgId)); dialog.setIndeterminate(indeterminate); dialog.setCancelable(isCancelable); if(isCancelable) { dialog.setButton(ProgressDialog.BUTTON_NEGATIVE, getText(android.R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if(which == ProgressDialog.BUTTON_NEGATIVE) activity.task.cancel(); } }); } if(!indeterminate) { dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); if(activity.max != 0) dialog.setMax(activity.max); } else dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); return dialog; } @Override public void onStart() { super.onStart(); if(!activity.dialogShowing) dialog.cancel(); } @Override public void onCancel(DialogInterface dialog) { activity.task.cancel(); super.onCancel(dialog); } } }