/*
* Copyright (C) 2009 University of Washington
*
* 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 org.odk.collect.android.activities;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import org.odk.collect.android.R;
import org.odk.collect.android.application.Collect;
import org.odk.collect.android.listeners.InstanceUploaderListener;
import org.odk.collect.android.preferences.PreferencesActivity;
import org.odk.collect.android.provider.InstanceProviderAPI.InstanceColumns;
import org.odk.collect.android.tasks.InstanceUploaderTask;
import org.odk.collect.android.utilities.WebUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
/**
* Activity to upload completed forms.
*
* @author Carl Hartung (carlhartung@gmail.com)
*/
public class InstanceUploaderActivity extends Activity implements InstanceUploaderListener {
private final static String t = "InstanceUploaderActivity";
private final static int PROGRESS_DIALOG = 1;
private final static int AUTH_DIALOG = 2;
private final static String AUTH_URI = "auth";
private static final String ALERT_MSG = "alertmsg";
private static final String ALERT_SHOWING = "alertshowing";
private static final String TO_SEND = "tosend";
private ProgressDialog mProgressDialog;
private AlertDialog mAlertDialog;
private String mAlertMsg;
private boolean mAlertShowing;
private InstanceUploaderTask mInstanceUploaderTask;
// maintain a list of what we've yet to send, in case we're interrupted by auth requests
private Long[] mInstancesToSend;
// maintain a list of what we've sent, in case we're interrupted by auth requests
private HashMap<String, String> mUploadedInstances;
private String mUrl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(t, "onCreate: " + ((savedInstanceState == null) ? "creating" : "re-initializing"));
mAlertMsg = getString(R.string.please_wait);
mAlertShowing = false;
mUploadedInstances = new HashMap<String, String>();
setTitle(getString(R.string.app_name) + " > " + getString(R.string.send_data));
// get any simple saved state...
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(ALERT_MSG)) {
mAlertMsg = savedInstanceState.getString(ALERT_MSG);
}
if (savedInstanceState.containsKey(ALERT_SHOWING)) {
mAlertShowing = savedInstanceState.getBoolean(ALERT_SHOWING, false);
}
mUrl = savedInstanceState.getString(AUTH_URI);
}
// and if we are resuming, use the TO_SEND list of not-yet-sent submissions
// Otherwise, construct the list from the incoming intent value
long[] selectedInstanceIDs = null;
if (savedInstanceState != null && savedInstanceState.containsKey(TO_SEND)) {
selectedInstanceIDs = savedInstanceState.getLongArray(TO_SEND);
} else {
// get instances to upload...
Intent intent = getIntent();
selectedInstanceIDs = intent.getLongArrayExtra(FormEntryActivity.KEY_INSTANCES);
}
mInstancesToSend = new Long[(selectedInstanceIDs == null) ? 0 : selectedInstanceIDs.length];
if ( selectedInstanceIDs != null ) {
for ( int i = 0 ; i < selectedInstanceIDs.length ; ++i ) {
mInstancesToSend[i] = selectedInstanceIDs[i];
}
}
// at this point, we don't expect this to be empty...
if (mInstancesToSend.length == 0) {
Log.e(t, "onCreate: No instances to upload!");
// drop through -- everything will process through OK
} else {
Log.i(t, "onCreate: Beginning upload of " + mInstancesToSend.length + " instances!");
}
// get the task if we've changed orientations. If it's null it's a new upload.
mInstanceUploaderTask = (InstanceUploaderTask) getLastNonConfigurationInstance();
if (mInstanceUploaderTask == null) {
// setup dialog and upload task
showDialog(PROGRESS_DIALOG);
mInstanceUploaderTask = new InstanceUploaderTask();
// register this activity with the new uploader task
mInstanceUploaderTask.setUploaderListener(InstanceUploaderActivity.this);
mInstanceUploaderTask.execute(mInstancesToSend);
}
}
@Override
protected void onStart() {
super.onStart();
Collect.getInstance().getActivityLogger().logOnStart(this);
}
@Override
protected void onResume() {
Log.i(t, "onResume: Resuming upload of " + mInstancesToSend.length + " instances!");
if (mInstanceUploaderTask != null) {
mInstanceUploaderTask.setUploaderListener(this);
}
if (mAlertShowing) {
createAlertDialog(mAlertMsg);
}
super.onResume();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(ALERT_MSG, mAlertMsg);
outState.putBoolean(ALERT_SHOWING, mAlertShowing);
outState.putString(AUTH_URI, mUrl);
long[] toSend = new long[mInstancesToSend.length];
for ( int i = 0 ; i < mInstancesToSend.length ; ++i ) {
toSend[i] = mInstancesToSend[i];
}
outState.putLongArray(TO_SEND, toSend);
}
@Override
public Object onRetainNonConfigurationInstance() {
return mInstanceUploaderTask;
}
@Override
protected void onPause() {
Log.i(t, "onPause: Pausing upload of " + mInstancesToSend.length + " instances!");
super.onPause();
if (mAlertDialog != null && mAlertDialog.isShowing()) {
mAlertDialog.dismiss();
}
}
@Override
protected void onStop() {
Collect.getInstance().getActivityLogger().logOnStop(this);
super.onStop();
}
@Override
protected void onDestroy() {
if (mInstanceUploaderTask != null) {
mInstanceUploaderTask.setUploaderListener(null);
}
super.onDestroy();
}
@Override
public void uploadingComplete(HashMap<String, String> result) {
Log.i(t, "uploadingComplete: Processing results (" + result.size() + ") from upload of " + mInstancesToSend.length + " instances!");
try {
dismissDialog(PROGRESS_DIALOG);
} catch (Exception e) {
// tried to close a dialog not open. don't care.
}
StringBuilder selection = new StringBuilder();
Set<String> keys = result.keySet();
Iterator<String> it = keys.iterator();
String[] selectionArgs = new String[keys.size()];
int i = 0;
while (it.hasNext()) {
String id = it.next();
selection.append(InstanceColumns._ID + "=?");
selectionArgs[i++] = id;
if (i != keys.size()) {
selection.append(" or ");
}
}
StringBuilder message = new StringBuilder();
{
Cursor results = null;
try {
results = getContentResolver().query(InstanceColumns.CONTENT_URI,
null, selection.toString(), selectionArgs, null);
if (results.getCount() > 0) {
results.moveToPosition(-1);
while (results.moveToNext()) {
String name =
results.getString(results.getColumnIndex(InstanceColumns.DISPLAY_NAME));
String id = results.getString(results.getColumnIndex(InstanceColumns._ID));
message.append(name + " - " + result.get(id) + "\n\n");
}
} else {
message.append(getString(R.string.no_forms_uploaded));
}
} finally {
if ( results != null ) {
results.close();
}
}
}
createAlertDialog(message.toString().trim());
}
@Override
public void progressUpdate(int progress, int total) {
mAlertMsg = getString(R.string.sending_items, progress, total);
mProgressDialog.setMessage(mAlertMsg);
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case PROGRESS_DIALOG:
Collect.getInstance().getActivityLogger().logAction(this, "onCreateDialog.PROGRESS_DIALOG", "show");
mProgressDialog = new ProgressDialog(this);
DialogInterface.OnClickListener loadingButtonListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Collect.getInstance().getActivityLogger().logAction(this, "onCreateDialog.PROGRESS_DIALOG", "cancel");
dialog.dismiss();
mInstanceUploaderTask.cancel(true);
mInstanceUploaderTask.setUploaderListener(null);
finish();
}
};
mProgressDialog.setTitle(getString(R.string.uploading_data));
mProgressDialog.setMessage(mAlertMsg);
mProgressDialog.setIndeterminate(true);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
mProgressDialog.setCancelable(false);
mProgressDialog.setButton(getString(R.string.cancel), loadingButtonListener);
return mProgressDialog;
case AUTH_DIALOG:
Log.i(t, "onCreateDialog(AUTH_DIALOG): for upload of " + mInstancesToSend.length + " instances!");
Collect.getInstance().getActivityLogger().logAction(this, "onCreateDialog.AUTH_DIALOG", "show");
AlertDialog.Builder b = new AlertDialog.Builder(this);
LayoutInflater factory = LayoutInflater.from(this);
final View dialogView = factory.inflate(R.layout.server_auth_dialog, null);
// Get the server, username, and password from the settings
SharedPreferences settings =
PreferenceManager.getDefaultSharedPreferences(getBaseContext());
String server = mUrl;
if (server == null) {
Log.e(t, "onCreateDialog(AUTH_DIALOG): No failing mUrl specified for upload of " + mInstancesToSend.length + " instances!");
// if the bundle is null, we're looking for a formlist
String submissionUrl = getString(R.string.default_odk_submission);
server =
settings.getString(PreferencesActivity.KEY_SERVER_URL,
getString(R.string.default_server_url))
+ settings.getString(PreferencesActivity.KEY_SUBMISSION_URL, submissionUrl);
}
final String url = server;
Log.i(t, "Trying connecting to: " + url);
EditText username = (EditText) dialogView.findViewById(R.id.username_edit);
String storedUsername = settings.getString(PreferencesActivity.KEY_USERNAME, null);
username.setText(storedUsername);
EditText password = (EditText) dialogView.findViewById(R.id.password_edit);
String storedPassword = settings.getString(PreferencesActivity.KEY_PASSWORD, null);
password.setText(storedPassword);
b.setTitle(getString(R.string.server_requires_auth));
b.setMessage(getString(R.string.server_auth_credentials, url));
b.setView(dialogView);
b.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Collect.getInstance().getActivityLogger().logAction(this, "onCreateDialog.AUTH_DIALOG", "OK");
EditText username = (EditText) dialogView.findViewById(R.id.username_edit);
EditText password = (EditText) dialogView.findViewById(R.id.password_edit);
Uri u = Uri.parse(url);
WebUtils.addCredentials(username.getText().toString(), password.getText()
.toString(), u.getHost());
showDialog(PROGRESS_DIALOG);
mInstanceUploaderTask = new InstanceUploaderTask();
// register this activity with the new uploader task
mInstanceUploaderTask.setUploaderListener(InstanceUploaderActivity.this);
mInstanceUploaderTask.execute(mInstancesToSend);
}
});
b.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Collect.getInstance().getActivityLogger().logAction(this, "onCreateDialog.AUTH_DIALOG", "cancel");
finish();
}
});
b.setCancelable(false);
return b.create();
}
return null;
}
@Override
public void authRequest(Uri url, HashMap<String, String> doneSoFar) {
if (mProgressDialog.isShowing()) {
// should always be showing here
mProgressDialog.dismiss();
}
// add our list of completed uploads to "completed"
// and remove them from our toSend list.
ArrayList<Long> workingSet = new ArrayList<Long>();
Collections.addAll(workingSet, mInstancesToSend);
if (doneSoFar != null) {
Set<String> uploadedInstances = doneSoFar.keySet();
Iterator<String> itr = uploadedInstances.iterator();
while (itr.hasNext()) {
Long removeMe = Long.valueOf(itr.next());
boolean removed = workingSet.remove(removeMe);
if (removed) {
Log.i(t, removeMe
+ " was already sent, removing from queue before restarting task");
}
}
mUploadedInstances.putAll(doneSoFar);
}
// and reconstruct the pending set of instances to send
Long[] updatedToSend = new Long[workingSet.size()];
for ( int i = 0 ; i < workingSet.size() ; ++i ) {
updatedToSend[i] = workingSet.get(i);
}
mInstancesToSend = updatedToSend;
mUrl = url.toString();
showDialog(AUTH_DIALOG);
}
private void createAlertDialog(String message) {
Collect.getInstance().getActivityLogger().logAction(this, "createAlertDialog", "show");
mAlertDialog = new AlertDialog.Builder(this).create();
mAlertDialog.setTitle(getString(R.string.upload_results));
mAlertDialog.setMessage(message);
DialogInterface.OnClickListener quitListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
switch (i) {
case DialogInterface.BUTTON_POSITIVE: // ok
Collect.getInstance().getActivityLogger().logAction(this, "createAlertDialog", "OK");
// always exit this activity since it has no interface
mAlertShowing = false;
finish();
break;
}
}
};
mAlertDialog.setCancelable(false);
mAlertDialog.setButton(getString(R.string.ok), quitListener);
mAlertDialog.setIcon(android.R.drawable.ic_dialog_info);
mAlertShowing = true;
mAlertMsg = message;
mAlertDialog.show();
}
}