/*
* 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 com.radicaldynamic.groupinform.activities;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.Map.Entry;
import org.ektorp.Attachment;
import org.ektorp.AttachmentInputStream;
import org.ektorp.DocumentNotFoundException;
import org.ektorp.ReplicationStatus;
import org.odk.collect.android.utilities.FileUtils;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.webkit.MimeTypeMap;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemSelectedListener;
import com.radicaldynamic.gcmobile.android.activities.DataExportActivity;
import com.radicaldynamic.gcmobile.android.activities.DataImportActivity;
import com.radicaldynamic.gcmobile.android.build.FieldList;
import com.radicaldynamic.gcmobile.android.dialogs.FilterByAssignmentDialog;
import com.radicaldynamic.groupinform.R;
import com.radicaldynamic.groupinform.adapters.BrowserLongListAdapter;
import com.radicaldynamic.groupinform.adapters.BrowserShortListAdapter;
import com.radicaldynamic.groupinform.application.Collect;
import com.radicaldynamic.groupinform.documents.FormDefinition;
import com.radicaldynamic.groupinform.documents.FormInstance;
import com.radicaldynamic.groupinform.listeners.DefinitionImportListener;
import com.radicaldynamic.groupinform.listeners.SynchronizeFoldersListener;
import com.radicaldynamic.groupinform.listeners.ToggleOnlineStateListener;
import com.radicaldynamic.groupinform.logic.AccountDevice;
import com.radicaldynamic.groupinform.logic.AccountFolder;
import com.radicaldynamic.groupinform.repositories.FormDefinitionRepo;
import com.radicaldynamic.groupinform.repositories.FormInstanceRepo;
import com.radicaldynamic.groupinform.services.DatabaseService;
import com.radicaldynamic.groupinform.tasks.DefinitionImportTask;
import com.radicaldynamic.groupinform.tasks.SynchronizeFoldersTask;
import com.radicaldynamic.groupinform.tasks.ToggleOnlineStateTask;
import com.radicaldynamic.groupinform.utilities.Base64Coder;
import com.radicaldynamic.groupinform.utilities.DocumentUtils;
import com.radicaldynamic.groupinform.utilities.FileUtilsExtended;
import com.radicaldynamic.groupinform.xform.FormReader;
import com.radicaldynamic.groupinform.xform.FormWriter;
/**
* Responsible for displaying buttons to launch the major activities. Launches
* some activities based on returns of others.
*
* @author Carl Hartung (carlhartung@gmail.com)
* @author Yaw Anokwa (yanokwa@gmail.com)
*/
public class BrowserActivity extends ListActivity implements DefinitionImportListener, ToggleOnlineStateListener, SynchronizeFoldersListener
{
private static final String t = "BrowserActivity: ";
// Dialog status codes
private static final int DIALOG_COPY_TO_FOLDER = 0;
private static final int DIALOG_CREATE_TEMPLATE = 1;
private static final int DIALOG_FOLDER_OUTDATED = 2;
private static final int DIALOG_FOLDER_UNAVAILABLE = 3;
private static final int DIALOG_FORM_BUILDER_LAUNCH_ERROR = 4;
private static final int DIALOG_IMPORTING_TEMPLATE = 5;
private static final int DIALOG_INSTANCES_UNAVAILABLE = 6;
private static final int DIALOG_OFFLINE_ATTEMPT_FAILED = 7;
private static final int DIALOG_OFFLINE_MODE_UNAVAILABLE_FOLDERS = 8;
private static final int DIALOG_ONLINE_ATTEMPT_FAILED = 9;
private static final int DIALOG_ONLINE_STATE_CHANGING = 10;
private static final int DIALOG_REMOVE_FORM = 11;
private static final int DIALOG_RENAME_TEMPLATE = 12;
private static final int DIALOG_SEARCH_FILTER = 13;
private static final int DIALOG_TOGGLE_ONLINE_STATE = 14;
private static final int DIALOG_UNABLE_TO_COPY_DUPLICATE = 15;
private static final int DIALOG_UNABLE_TO_IMPORT_TEMPLATE = 16;
private static final int DIALOG_UNABLE_TO_RENAME_DUPLICATE = 17;
private static final int DIALOG_UPDATING_FOLDER = 18;
// Keys for option menu items
private static final int MENU_OPTION_REFRESH = 0;
private static final int MENU_OPTION_SEARCH = 1;
private static final int MENU_OPTION_FOLDERS = 2;
private static final int MENU_OPTION_NEWFORM = 3;
private static final int MENU_OPTION_ODKTOOLS = 4;
private static final int MENU_OPTION_INFO = 5;
// Keys for context menu items
private static final int MENU_CONTEXT_COPY = 0;
private static final int MENU_CONTEXT_EDIT = 1;
private static final int MENU_CONTEXT_EXPORT = 2;
private static final int MENU_CONTEXT_IMPORT = 3;
private static final int MENU_CONTEXT_REMOVE = 4;
private static final int MENU_CONTEXT_RENAME = 5;
// Keys for persistence between screen orientation changes
private static final String KEY_COPY_TO_FOLDER_AS = "copy_to_folder_as";
private static final String KEY_COPY_TO_FOLDER_ID = "copy_to_folder_id";
private static final String KEY_COPY_TO_FOLDER_NAME = "copy_to_folder_name";
private static final String KEY_DIALOG_MESSAGE = "dialog_msg";
private static final String KEY_FORM_DEFINITION = "form_definition_doc";
private static final String KEY_SELECTED_DB = "selected_db";
private static final String KEY_SEARCH_FILTER = "search_filter";
private static final String KEY_TASK_SELECTOR = "task_selector";
// Search filter keys
private static final String KEY_SEARCH_BY_ASSIGNMENT = "search_by_assignment";
public static final String KEY_SEARCH_BY_ASSIGNMENT_IDS = "search_by_assignment_ids";
private static final String KEY_SEARCH_BY_STATUS = "search_by_status";
// Request codes for returning data from specified intent
private static final int RESULT_ABOUT = 1;
private static final int RESULT_COPY = 2;
private static final int RESULT_IMPORT = 3;
private FormDefinition mFormDefinition; // Stash for a selected form definition
private Bundle mSearchFilter = null;
private String mCopyToFolderId; // Data passed back from user selection on AccountFolderList
private String mCopyToFolderName; // Same
private String mCopyToFolderAs; // Used to pass to DIALOG_UNABLE_TO_COPY_DUPLICATE
private String mSelectedDatabase; // To save & restore the currently selected database
private CopyToFolderTask mCopyToFolderTask;
private DefinitionImportTask mDefinitionImportTask;
private RefreshViewTask mRefreshViewTask;
private RemoveDefinitionTask mRemoveDefinitionTask;
private RenameDefinitionTask mRenameDefinitionTask;
private SynchronizeFoldersTask mSynchronizeFoldersTask;
private ToggleOnlineStateTask mToggleOnlineStateTask;
private UpdateFolderTask mUpdateFolderTask;
private Dialog mDialog;
private String mDialogMessage; // Custom message consumed by onCreateDialog()
private ProgressDialog mProgressDialog;
@SuppressWarnings("unchecked")
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.browser);
// Load our custom window title
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.title_browser_activity);
// Initiate and populate spinner to filter form browser on the basis of the currently selected task
ArrayAdapter<String> taskSpinnerOptions = new ArrayAdapter(this, android.R.layout.simple_spinner_item);
String [] taskOptions = getResources().getStringArray(R.array.tf_task_selector_options);
// Filter tasks for certain device roles
for (String t : taskOptions) {
if (Collect.getInstance().getInformOnlineState().getDeviceRole().equals(AccountDevice.ROLE_DATA_ENTRY)) {
if (!t.equals("Export Records") && !t.equals("Edit Form Templates")) {
taskSpinnerOptions.add(t);
}
} else {
taskSpinnerOptions.add(t);
}
}
taskSpinnerOptions.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// Associate task spinner with options, set up listener for spinner
Spinner s1 = (Spinner) findViewById(R.id.taskSpinner);
s1.setAdapter(taskSpinnerOptions);
if (savedInstanceState == null) {
mDialogMessage = "";
mSelectedDatabase = null;
} else {
// For "copy to folder" operation, restore destination folder
if (savedInstanceState.containsKey(KEY_COPY_TO_FOLDER_AS))
mCopyToFolderAs = savedInstanceState.getString(KEY_COPY_TO_FOLDER_AS);
if (savedInstanceState.containsKey(KEY_COPY_TO_FOLDER_ID))
mCopyToFolderId = savedInstanceState.getString(KEY_COPY_TO_FOLDER_ID);
if (savedInstanceState.containsKey(KEY_COPY_TO_FOLDER_NAME))
mCopyToFolderName = savedInstanceState.getString(KEY_COPY_TO_FOLDER_NAME);
if (savedInstanceState.containsKey(KEY_DIALOG_MESSAGE))
mDialogMessage = savedInstanceState.getString(KEY_DIALOG_MESSAGE);
if (savedInstanceState.containsKey(KEY_SELECTED_DB))
mSelectedDatabase = savedInstanceState.getString(KEY_SELECTED_DB);
if (savedInstanceState.containsKey(KEY_TASK_SELECTOR))
s1.setSelection(savedInstanceState.getInt(KEY_TASK_SELECTOR), true);
}
// Retrieve persistent data structures and processes
Object data = getLastNonConfigurationInstance();
if (data instanceof DefinitionImportTask) {
mDefinitionImportTask = (DefinitionImportTask) data;
} else if (data instanceof SynchronizeFoldersTask) {
mSynchronizeFoldersTask = (SynchronizeFoldersTask) data;
} else if (data instanceof ToggleOnlineStateTask) {
mToggleOnlineStateTask = (ToggleOnlineStateTask) data;
} else if (data instanceof HashMap<?, ?>) {
mFormDefinition = (FormDefinition) ((HashMap<String, Object>) data).get(KEY_FORM_DEFINITION);
mSearchFilter = (Bundle) ((HashMap<String, Object>) data).get(KEY_SEARCH_FILTER);
}
s1.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
if (mRefreshViewTask == null || mRefreshViewTask.getStatus() == AsyncTask.Status.FINISHED)
loadScreen();
}
public void onNothingSelected(AdapterView<?> parent) { }
});
// Set up listener for Folder title button
((Button) findViewById(R.id.folderTitleButton)).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v)
{
startActivity(new Intent(BrowserActivity.this, AccountFolderList.class));
}
});
// Set up listener for Online/Offline title button
((Button) findViewById(R.id.onlineStatusTitleButton)).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v)
{
if (Collect.getInstance().getInformOnlineState().hasReplicatedFolders())
showDialog(DIALOG_TOGGLE_ONLINE_STATE);
else
showDialog(DIALOG_OFFLINE_MODE_UNAVAILABLE_FOLDERS);
}
});
}
@Override
protected void onDestroy()
{
if (mProgressDialog != null && mProgressDialog.isShowing())
mProgressDialog.dismiss();
// Clean up definition import task
if (mDefinitionImportTask != null) {
mDefinitionImportTask.setListener(null);
if (mDefinitionImportTask.getStatus() == AsyncTask.Status.FINISHED) {
mDefinitionImportTask.cancel(true);
}
}
// Clean up folder synchronization task
if (mSynchronizeFoldersTask != null) {
mSynchronizeFoldersTask.setListener(null);
if (mSynchronizeFoldersTask.getStatus() == AsyncTask.Status.FINISHED) {
mSynchronizeFoldersTask.cancel(true);
}
}
// Clean up online/offline toggle task
if (mToggleOnlineStateTask != null) {
mToggleOnlineStateTask.setListener(null);
if (mToggleOnlineStateTask.getStatus() == AsyncTask.Status.FINISHED) {
mToggleOnlineStateTask.cancel(true);
}
}
super.onDestroy();
}
@Override
protected void onResume()
{
super.onResume();
// Handle resume of definition import task
if (mDefinitionImportTask != null) {
mDefinitionImportTask.setListener(this);
if (mDefinitionImportTask != null && mDefinitionImportTask.getStatus() == AsyncTask.Status.FINISHED) {
dismissDialog(DIALOG_IMPORTING_TEMPLATE);
}
}
// Handle resume of folder synchronization task
if (mSynchronizeFoldersTask != null) {
mSynchronizeFoldersTask.setListener(this);
if (mSynchronizeFoldersTask != null) {
if (mSynchronizeFoldersTask.getStatus() == AsyncTask.Status.RUNNING) {
synchronizationHandler(null);
} else if (mSynchronizeFoldersTask.getStatus() == AsyncTask.Status.FINISHED) {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.dismiss();
}
}
}
}
// Handle resume of toggle online state offline/online task
if (mToggleOnlineStateTask != null) {
mToggleOnlineStateTask.setListener(this);
if (mToggleOnlineStateTask != null && mToggleOnlineStateTask.getStatus() == AsyncTask.Status.FINISHED) {
removeDialog(DIALOG_ONLINE_STATE_CHANGING);
}
}
loadScreen();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent)
{
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode == RESULT_CANCELED)
return;
switch (requestCode) {
case RESULT_ABOUT:
// "Exit" if the user resets GC Mobile
Intent i = new Intent();
i.putExtra("exit_app", true);
setResult(RESULT_OK, i);
finish();
break;
case RESULT_COPY:
mCopyToFolderId = intent.getStringExtra(AccountFolderList.KEY_FOLDER_ID);
mCopyToFolderName = intent.getStringExtra(AccountFolderList.KEY_FOLDER_NAME);
showDialog(DIALOG_COPY_TO_FOLDER);
break;
case RESULT_IMPORT:
showDialog(DIALOG_IMPORTING_TEMPLATE);
mDefinitionImportTask = new DefinitionImportTask();
mDefinitionImportTask.setListener(this);
mDefinitionImportTask.execute(intent.getStringExtra(FileDialog.RESULT_PATH));
break;
}
}
@Override
public boolean onContextItemSelected(MenuItem item)
{
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
FormDefinition fd = (FormDefinition) getListAdapter().getItem((int) info.id);
Intent i;
switch (item.getItemId()) {
case MENU_CONTEXT_COPY:
mFormDefinition = fd;
i = new Intent(this, AccountFolderList.class);
i.putExtra(AccountFolderList.KEY_COPY_TO_FOLDER, true);
startActivityForResult(i, RESULT_COPY);
return true;
case MENU_CONTEXT_EDIT:
FormBuilderLauncherTask fbl = new FormBuilderLauncherTask();
fbl.execute(fd.getId());
return true;
case MENU_CONTEXT_EXPORT:
i = new Intent(this, DataExportActivity.class);
i.putExtra(FormEntryActivity.KEY_FORMPATH, fd.getId());
startActivity(i);
return true;
case MENU_CONTEXT_IMPORT:
i = new Intent(this, DataImportActivity.class);
i.putExtra(FormEntryActivity.KEY_FORMPATH, fd.getId());
startActivity(i);
return true;
case MENU_CONTEXT_REMOVE:
mFormDefinition = fd;
showDialog(DIALOG_REMOVE_FORM);
return true;
case MENU_CONTEXT_RENAME:
mFormDefinition = fd;
showDialog(DIALOG_RENAME_TEMPLATE);
return true;
default:
return super.onContextItemSelected(item);
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)
{
super.onCreateContextMenu(menu, v, menuInfo);
if (!Collect.getInstance().getInformOnlineState().getDeviceRole().equals(AccountDevice.ROLE_DATA_ENTRY)
&& ((Spinner) findViewById(R.id.taskSpinner)).getSelectedItemPosition() != 3)
{
menu.add(0, MENU_CONTEXT_COPY, 0, getString(R.string.tf_copy_to_folder));
menu.add(0, MENU_CONTEXT_EDIT, 0, getString(R.string.tf_edit_template));
menu.add(0, MENU_CONTEXT_EXPORT, 0, getString(R.string.tf_export_records));
menu.add(0, MENU_CONTEXT_IMPORT, 0, getString(R.string.tf_import_records));
menu.add(0, MENU_CONTEXT_REMOVE, 0, getString(R.string.tf_remove_template));
menu.add(0, MENU_CONTEXT_RENAME, 0, getString(R.string.tf_rename_template));
}
}
public Dialog onCreateDialog(int id)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = null;
mDialog = null;
if (isFinishing()) {
return mDialog;
}
switch (id) {
// User wishes to make a new form
case DIALOG_CREATE_TEMPLATE:
view = inflater.inflate(R.layout.dialog_create_or_rename_form, null);
// Set an EditText view to get user input
final EditText newFormName = (EditText) view.findViewById(R.id.formName);
builder.setView(view);
builder.setInverseBackgroundForced(true);
builder.setTitle(getText(R.string.tf_add_template));
builder.setPositiveButton(getText(R.string.tf_create), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
FormDefinition form = new FormDefinition();
form.setName(newFormName.getText().toString().trim());
form.setStatus(FormDefinition.Status.placeholder);
if (form.getName().length() == 0) {
removeDialog(DIALOG_CREATE_TEMPLATE);
Toast.makeText(getApplicationContext(), getString(R.string.tf_form_name_required), Toast.LENGTH_LONG).show();
showDialog(DIALOG_CREATE_TEMPLATE);
} else {
removeDialog(DIALOG_CREATE_TEMPLATE);
new CreateFormDefinitionTask().execute(form);
}
}
});
builder.setNeutralButton(getString(R.string.tf_import), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(BrowserActivity.this, FileDialog.class);
intent.putExtra(FileDialog.SELECTION_MODE, FileDialog.MODE_OPEN);
intent.putExtra(FileDialog.START_PATH, "/sdcard");
intent.putExtra(FileDialog.WINDOW_TITLE, "Select XForm File To Import");
startActivityForResult(intent, RESULT_IMPORT);
}
});
builder.setNegativeButton(getText(R.string.cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
removeDialog(DIALOG_CREATE_TEMPLATE);
}
});
mDialog = builder.create();
break;
case DIALOG_COPY_TO_FOLDER:
view = inflater.inflate(R.layout.dialog_copy_to_folder, null);
// Set an EditText view to get user input
final TextView copyDestination = (TextView) view.findViewById(R.id.copyDestination);
final EditText copyName = (EditText) view.findViewById(R.id.copyName);
copyDestination.setText(mCopyToFolderName);
copyName.setText(mFormDefinition.getName());
builder
.setTitle(R.string.tf_copy_to_folder)
.setView(view)
.setInverseBackgroundForced(true);
builder.setPositiveButton(getText(R.string.tf_copy), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String copyAsName = copyName.getText().toString().trim();
if (copyAsName.length() > 0) {
mCopyToFolderAs = copyAsName;
mCopyToFolderTask = new CopyToFolderTask();
mCopyToFolderTask.execute(mFormDefinition, mCopyToFolderId, copyAsName);
removeDialog(DIALOG_COPY_TO_FOLDER);
} else {
removeDialog(DIALOG_COPY_TO_FOLDER);
Toast.makeText(getApplicationContext(), getString(R.string.tf_form_name_required), Toast.LENGTH_LONG).show();
showDialog(DIALOG_COPY_TO_FOLDER);
}
}
});
builder.setNegativeButton(getText(R.string.cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
removeDialog(DIALOG_COPY_TO_FOLDER);
}
});
mDialog = builder.create();
break;
// Local folder is most likely out-of-date
case DIALOG_FOLDER_OUTDATED:
builder
.setCancelable(false)
.setIcon(R.drawable.ic_dialog_info)
.setTitle(R.string.tf_folder_outdated_dialog)
.setMessage(getString(R.string.tf_folder_outdated_dialog_msg, getSelectedFolderName()));
builder.setPositiveButton(getString(R.string.tf_update), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
removeDialog(DIALOG_FOLDER_OUTDATED);
mUpdateFolderTask = new UpdateFolderTask();
mUpdateFolderTask.execute();
}
});
builder.setNeutralButton(getString(R.string.tf_form_folders), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
startActivity(new Intent(BrowserActivity.this, AccountFolderList.class));
removeDialog(DIALOG_FOLDER_OUTDATED);
}
});
mDialog = builder.create();
break;
// Couldn't connect to DB (for a specific reason)
case DIALOG_FOLDER_UNAVAILABLE:
builder
.setCancelable(false)
.setIcon(R.drawable.ic_dialog_info)
.setTitle(R.string.tf_folder_unavailable)
.setMessage(mDialogMessage);
builder.setPositiveButton(getString(R.string.tf_form_folders), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
startActivity(new Intent(BrowserActivity.this, AccountFolderList.class));
removeDialog(DIALOG_FOLDER_UNAVAILABLE);
}
});
if (!Collect.getInstance().getIoService().isSignedIn()) {
builder.setNeutralButton(getString(R.string.tf_go_online), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
removeDialog(DIALOG_FOLDER_UNAVAILABLE);
mToggleOnlineStateTask = new ToggleOnlineStateTask();
mToggleOnlineStateTask.setListener(BrowserActivity.this);
mToggleOnlineStateTask.execute();
}
});
}
mDialog = builder.create();
break;
// Unable to launch form builder (instances present)
case DIALOG_FORM_BUILDER_LAUNCH_ERROR:
builder
.setIcon(R.drawable.ic_dialog_info)
.setTitle(R.string.tf_unable_to_launch_form_builder_dialog)
.setMessage(R.string.tf_unable_to_launch_form_builder_dialog_msg);
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.cancel();
}
});
mDialog = builder.create();
break;
// Simple progress dialog to display while importing a definition to the current folder
case DIALOG_IMPORTING_TEMPLATE:
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage(getText(R.string.tf_importing_template_please_wait));
mProgressDialog.setIndeterminate(true);
mProgressDialog.setCancelable(false);
return mProgressDialog;
// User requested forms (definitions or instances) to be loaded but none could be found
case DIALOG_INSTANCES_UNAVAILABLE:
builder
.setCancelable(false)
.setIcon(R.drawable.ic_dialog_info)
.setTitle(R.string.tf_unable_to_load_instances_dialog)
.setMessage(R.string.tf_unable_to_load_instances_dialog_msg);
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
loadScreen();
dialog.cancel();
}
});
mDialog = builder.create();
break;
// We can't go offline (user has not selected any databases to be replicated)
case DIALOG_OFFLINE_MODE_UNAVAILABLE_FOLDERS:
builder
.setCancelable(false)
.setIcon(R.drawable.ic_dialog_info)
.setTitle(R.string.tf_unable_to_go_offline_dialog)
.setMessage(R.string.tf_unable_to_go_offline_dialog_msg_reason_folders);
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.cancel();
}
});
mDialog = builder.create();
break;
// Simple progress dialog for online/offline
case DIALOG_ONLINE_STATE_CHANGING:
if (Collect.getInstance().getIoService().isSignedIn())
mDialog = ProgressDialog.show(this, "", getText(R.string.tf_inform_state_disconnecting));
else
mDialog = ProgressDialog.show(this, "", getText(R.string.tf_inform_state_connecting));
break;
// Tried going offline but couldn't
case DIALOG_OFFLINE_ATTEMPT_FAILED:
builder
.setCancelable(false)
.setIcon(R.drawable.ic_dialog_alert)
.setTitle(R.string.tf_unable_to_go_offline_dialog)
.setMessage(R.string.tf_unable_to_go_offline_dialog_msg_reason_generic);
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
loadScreen();
dialog.cancel();
}
});
mDialog = builder.create();
break;
// Tried going online but couldn't
case DIALOG_ONLINE_ATTEMPT_FAILED:
builder
.setCancelable(false)
.setIcon(R.drawable.ic_dialog_alert)
.setTitle(R.string.tf_unable_to_go_online_dialog)
.setMessage(R.string.tf_unable_to_go_online_dialog_msg_reason_generic);
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
loadScreen();
dialog.cancel();
}
});
mDialog = builder.create();
break;
case DIALOG_REMOVE_FORM:
String removeFormMessage = getString(R.string.tf_remove_form_without_instances_dialog_msg, mFormDefinition.getName());
try {
// Determine if draft or complete instances exist for this definition
if (new FormInstanceRepo(Collect.getInstance().getDbService().getDb()).findByFormId(mFormDefinition.getId()).size() > 0) {
removeFormMessage = getString(R.string.tf_remove_form_with_instances_dialog_msg, mFormDefinition.getName());
}
} catch (Exception e) {
if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "unexpected exception while processing DIALOG_REMOVE_FORM");
e.printStackTrace();
}
builder
.setIcon(R.drawable.ic_dialog_alert)
.setTitle(R.string.tf_remove_form)
.setMessage(removeFormMessage);
builder.setPositiveButton(getString(R.string.tf_remove), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
removeDialog(DIALOG_REMOVE_FORM);
mRemoveDefinitionTask = new RemoveDefinitionTask();
mRemoveDefinitionTask.execute(mFormDefinition);
}
});
builder.setNegativeButton(getText(R.string.cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
removeDialog(DIALOG_REMOVE_FORM);
}
});
mDialog = builder.create();
break;
case DIALOG_RENAME_TEMPLATE:
view = inflater.inflate(R.layout.dialog_create_or_rename_form, null);
// Set an EditText view to get user input
final EditText renamedFormName = (EditText) view.findViewById(R.id.formName);
TextView renamedFormNameHint = (TextView) view.findViewById(R.id.formNameHint);
renamedFormNameHint.setText(getString(R.string.tf_rename_template_hint));
builder
.setView(view)
.setInverseBackgroundForced(true)
.setTitle(getText(R.string.tf_rename_template));
renamedFormName.setText(mFormDefinition.getName());
builder.setPositiveButton(getText(R.string.tf_rename), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String newName = renamedFormName.getText().toString().trim();
if (newName.length() == 0) {
removeDialog(DIALOG_RENAME_TEMPLATE);
Toast.makeText(getApplicationContext(), getString(R.string.tf_form_name_required), Toast.LENGTH_LONG).show();
showDialog(DIALOG_RENAME_TEMPLATE);
} else {
if (newName.equals(mFormDefinition.getName())) {
// Do nothing
} else {
// Hijack this variable in case we need to display DIALOG_UNABLE_TO_RENAME_DUPLICATE
mCopyToFolderAs = newName;
mRenameDefinitionTask = new RenameDefinitionTask();
mRenameDefinitionTask.execute(mFormDefinition, newName);
}
}
}
});
builder.setNegativeButton(getText(R.string.cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
removeDialog(DIALOG_RENAME_TEMPLATE);
}
});
mDialog = builder.create();
break;
case DIALOG_SEARCH_FILTER:
view = inflater.inflate(R.layout.dialog_search_forms, null);
if (mSearchFilter == null)
mSearchFilter = new Bundle();
// Set up adapter and spinner for assignment filter
ArrayAdapter<String> filterByAssignmentOptions =
new ArrayAdapter<String>(
this,
android.R.layout.simple_spinner_item,
new ArrayList<String>(Arrays.asList("Any device", "This device", "Other devices")));
filterByAssignmentOptions.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
final Spinner filterByAssignment = (Spinner) view.findViewById(R.id.filterByAssignment);
filterByAssignment.setAdapter(filterByAssignmentOptions);
filterByAssignment.setSelection(mSearchFilter.getInt(KEY_SEARCH_BY_ASSIGNMENT, 0));
filterByAssignment.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
// Pop up profile selection dialog
if (position == 2 && mSearchFilter.getInt(KEY_SEARCH_BY_ASSIGNMENT, 0) != 2) {
FilterByAssignmentDialog assignmentDialog = new FilterByAssignmentDialog(BrowserActivity.this, mSearchFilter);
assignmentDialog.show();
}
mSearchFilter.putInt(KEY_SEARCH_BY_ASSIGNMENT, position);
}
@Override
public void onNothingSelected(AdapterView<?> arg0)
{
}
});
// Set up adapter and spinner for status filter
ArrayAdapter<String> filterByStatusOptions =
new ArrayAdapter<String>(
this,
android.R.layout.simple_spinner_item,
new ArrayList<String>(Arrays.asList("Any status", "Complete status", "Draft status")));
filterByStatusOptions.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
final Spinner filterByStatus = (Spinner) view.findViewById(R.id.filterByStatus);
filterByStatus.setAdapter(filterByStatusOptions);
filterByStatus.setSelection(mSearchFilter.getInt(KEY_SEARCH_BY_STATUS, 0));
filterByStatus.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
mSearchFilter.putInt(KEY_SEARCH_BY_STATUS, position);
}
@Override
public void onNothingSelected(AdapterView<?> arg0)
{
}
});
builder
.setView(view)
.setInverseBackgroundForced(true)
.setTitle("Search Form List");
builder.setPositiveButton(getString(R.string.tf_search), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
removeDialog(DIALOG_SEARCH_FILTER);
if (((Spinner) findViewById(R.id.taskSpinner)).getSelectedItemPosition() != 3)
((Spinner) findViewById(R.id.taskSpinner)).setSelection(3, true);
else
loadScreen();
}
});
builder.setNegativeButton(getString(R.string.tf_clear_results), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
removeDialog(DIALOG_SEARCH_FILTER);
mSearchFilter = null;
if (((Spinner) findViewById(R.id.taskSpinner)).getSelectedItemPosition() != 3)
((Spinner) findViewById(R.id.taskSpinner)).setSelection(3, true);
else
loadScreen();
}
});
mDialog = builder.create();
break;
case DIALOG_TOGGLE_ONLINE_STATE:
view = inflater.inflate(R.layout.dialog_toggle_online_state, null);
final CheckBox synchronizeFolders = (CheckBox) view.findViewById(R.id.synchronizeFolders);
TextView synchronizeFoldersMessage = (TextView) view.findViewById(R.id.synchronizeFoldersMessage);
String buttonText;
builder
.setView(view)
.setInverseBackgroundForced(true)
.setIcon(R.drawable.ic_dialog_info);
if (Collect.getInstance().getIoService().isSignedIn()) {
builder.setTitle(getText(R.string.tf_go_offline) + "?");
synchronizeFoldersMessage.setText(getString(R.string.tf_go_offline_dialog_msg));
buttonText = getText(R.string.tf_go_offline).toString();
} else {
builder.setTitle(getText(R.string.tf_go_online) + "?");
synchronizeFoldersMessage.setText(getString(R.string.tf_go_online_dialog_msg));
buttonText = getText(R.string.tf_go_online).toString();
}
builder.setPositiveButton(buttonText, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
removeDialog(DIALOG_TOGGLE_ONLINE_STATE);
if (Collect.getInstance().getIoService().isSignedIn() && synchronizeFolders.isChecked()) {
mSynchronizeFoldersTask = new SynchronizeFoldersTask();
mSynchronizeFoldersTask.setListener(BrowserActivity.this);
mSynchronizeFoldersTask.setTransferMode(SynchronizeFoldersListener.MODE_SWAP);
mSynchronizeFoldersTask.setPostExecuteSwitch(true);
mSynchronizeFoldersTask.execute();
} else {
mToggleOnlineStateTask = new ToggleOnlineStateTask();
mToggleOnlineStateTask.setListener(BrowserActivity.this);
if (synchronizeFolders.isChecked()) {
mToggleOnlineStateTask.setPostExecuteSwitch(true);
}
mToggleOnlineStateTask.execute();
}
}
});
builder.setNegativeButton(getText(R.string.cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
removeDialog(DIALOG_TOGGLE_ONLINE_STATE);
}
});
mDialog = builder.create();
break;
case DIALOG_UNABLE_TO_COPY_DUPLICATE:
builder
.setCancelable(false)
.setIcon(R.drawable.ic_dialog_alert)
.setTitle(R.string.tf_unable_to_copy)
.setMessage(getString(R.string.tf_unable_to_copy_duplicate_dialog_msg, mCopyToFolderName, mCopyToFolderAs));
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
removeDialog(DIALOG_UNABLE_TO_COPY_DUPLICATE);
showDialog(DIALOG_COPY_TO_FOLDER);
}
});
mDialog = builder.create();
break;
case DIALOG_UNABLE_TO_IMPORT_TEMPLATE:
builder
.setCancelable(false)
.setIcon(R.drawable.ic_dialog_alert)
.setTitle(R.string.tf_unable_to_import_template)
.setMessage(mDialogMessage);
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
removeDialog(DIALOG_UNABLE_TO_IMPORT_TEMPLATE);
}
});
mDialog = builder.create();
break;
case DIALOG_UNABLE_TO_RENAME_DUPLICATE:
builder
.setCancelable(false)
.setIcon(R.drawable.ic_dialog_alert)
.setTitle(R.string.tf_unable_to_rename_dialog)
.setMessage(getString(R.string.tf_unable_to_rename_dialog_msg, mCopyToFolderAs));
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
removeDialog(DIALOG_UNABLE_TO_RENAME_DUPLICATE);
showDialog(DIALOG_RENAME_TEMPLATE);
}
});
mDialog = builder.create();
break;
case DIALOG_UPDATING_FOLDER:
mDialog = ProgressDialog.show(this, "", getString(R.string.tf_updating_with_param, getSelectedFolderName()));
break;
}
return mDialog;
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
super.onCreateOptionsMenu(menu);
menu.add(0, MENU_OPTION_REFRESH, 0, getString(R.string.refresh))
.setIcon(R.drawable.ic_menu_refresh);
menu.add(0, MENU_OPTION_SEARCH, 0, getString(R.string.tf_search))
.setIcon(R.drawable.ic_menu_search);
menu.add(0, MENU_OPTION_FOLDERS, 0, getString(R.string.tf_form_folders))
.setIcon(R.drawable.ic_menu_archive);
if (!Collect.getInstance().getInformOnlineState().getDeviceRole().equals(AccountDevice.ROLE_DATA_ENTRY)) {
menu.add(0, MENU_OPTION_NEWFORM, 0, getString(R.string.tf_add_template))
.setIcon(R.drawable.ic_menu_add);
}
menu.add(0, MENU_OPTION_ODKTOOLS, 0, getString(R.string.open_data_kit))
.setIcon(R.drawable.ic_menu_upload);
menu.add(0, MENU_OPTION_INFO, 0, getString(R.string.tf_inform_info))
.setIcon(R.drawable.ic_menu_info_details);
return true;
}
/*
* (non-Javadoc)
*
* @see android.app.Activity#onKeyDown(int, android.view.KeyEvent)
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
setResult(RESULT_OK);
finish();
}
return super.onKeyDown(keyCode, event);
}
/**
* Stores the path of selected form and finishes.
*/
@Override
protected void onListItemClick(ListView listView, View view, int position, long id)
{
InstanceLoadPathTask ilp;
Intent i;
switch (((Spinner) findViewById(R.id.taskSpinner)).getSelectedItemPosition()) {
case 0:
// When showing all forms in folder... start a new form
i = new Intent(this, FormEntryActivity.class);
i.putStringArrayListExtra(FormEntryActivity.KEY_INSTANCES, new ArrayList<String>());
i.putExtra(FormEntryActivity.KEY_FORMPATH, ((FormDefinition) getListAdapter().getItem(position)).getId());
startActivity(i);
break;
case 1:
// When showing all completed forms in folder... browse selected form instances
ilp = new InstanceLoadPathTask();
ilp.execute(((FormDefinition) getListAdapter().getItem(position)).getId(), FormInstance.Status.complete);
break;
case 2:
// When showing all draft forms in folder... browse selected form instances
ilp = new InstanceLoadPathTask();
ilp.execute(((FormDefinition) getListAdapter().getItem(position)).getId(), FormInstance.Status.draft);
break;
case 3:
// Load instance for editing
String instanceId = ((FormInstance) getListAdapter().getItem(position)).getId();
i = new Intent(this, FormEntryActivity.class);
i.putStringArrayListExtra(FormEntryActivity.KEY_INSTANCES, new ArrayList<String>(Arrays.asList(instanceId)));
i.putExtra(FormEntryActivity.KEY_INSTANCEPATH, instanceId);
i.putExtra(FormEntryActivity.KEY_FORMPATH, ((FormInstance) getListAdapter().getItem(position)).getFormId());
startActivity(i);
break;
case 4:
// When showing all forms in folder... export records
Intent dea = new Intent(this, DataExportActivity.class);
dea.putExtra(FormEntryActivity.KEY_FORMPATH, ((FormDefinition) getListAdapter().getItem(position)).getId());
startActivity(dea);
break;
case 5:
// When showing all forms in folder... edit a form
FormBuilderLauncherTask fbl = new FormBuilderLauncherTask();
fbl.execute(((FormDefinition) getListAdapter().getItem(position)).getId());
break;
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId()) {
case MENU_OPTION_REFRESH:
loadScreen();
break;
case MENU_OPTION_SEARCH:
showDialog(DIALOG_SEARCH_FILTER);
break;
case MENU_OPTION_FOLDERS:
startActivity(new Intent(this, AccountFolderList.class));
break;
case MENU_OPTION_NEWFORM:
showDialog(DIALOG_CREATE_TEMPLATE);
break;
case MENU_OPTION_ODKTOOLS:
startActivity(new Intent(this, ODKActivityTab.class));
break;
case MENU_OPTION_INFO:
startActivityForResult(new Intent(this, ClientInformationActivity.class), RESULT_ABOUT);
return true;
}
return super.onOptionsItemSelected(item);
}
// Pass references and other important information to the next thread
@Override
public Object onRetainNonConfigurationInstance()
{
if (mDefinitionImportTask != null && mDefinitionImportTask.getStatus() != AsyncTask.Status.FINISHED)
return mDefinitionImportTask;
if (mSynchronizeFoldersTask != null && mSynchronizeFoldersTask.getStatus() != AsyncTask.Status.FINISHED)
return mSynchronizeFoldersTask;
if (mToggleOnlineStateTask != null && mToggleOnlineStateTask.getStatus() != AsyncTask.Status.FINISHED)
return mToggleOnlineStateTask;
// Avoid refetching documents from database by preserving them
HashMap<String, Object> data = new HashMap<String, Object>();
data.put(KEY_FORM_DEFINITION, mFormDefinition);
data.put(KEY_SEARCH_FILTER, mSearchFilter);
return data;
}
@Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putString(KEY_COPY_TO_FOLDER_AS, mCopyToFolderAs);
outState.putString(KEY_COPY_TO_FOLDER_ID, mCopyToFolderId);
outState.putString(KEY_COPY_TO_FOLDER_NAME, mCopyToFolderName);
outState.putString(KEY_DIALOG_MESSAGE, mDialogMessage);
outState.putString(KEY_SELECTED_DB, mSelectedDatabase);
outState.putInt(KEY_TASK_SELECTOR, ((Spinner) findViewById(R.id.taskSpinner)).getSelectedItemPosition());
}
private class CopyToFolderTask extends AsyncTask<Object, Void, Void>
{
private static final String tt = t + "CopyToFolderTask: ";
private static final String KEY_ITEM = "key_item";
private boolean copied = false;
private boolean duplicate = false;
private String copyFormAsName;
private String copyToFolderId;
private FormDefinition formDefinition;
ProgressDialog progressDialog = null;
final Handler progressHandler = new Handler() {
public void handleMessage(Message msg) {
progressDialog.setMessage(getString(R.string.tf_copying_with_param, msg.getData().getString(KEY_ITEM)));
}
};
@Override
protected Void doInBackground(Object... params)
{
formDefinition = (FormDefinition) params[0];
copyToFolderId = (String) params[1];
copyFormAsName = (String) params[2];
if (Collect.Log.DEBUG) Log.d(Collect.LOGTAG, tt + "about to copy " + formDefinition.getId() + " to " + copyToFolderId);
Message msg = progressHandler.obtainMessage();
Bundle b = new Bundle();
b.putString(KEY_ITEM, formDefinition.getName());
msg.setData(b);
progressHandler.sendMessage(msg);
AttachmentInputStream ais = null;;
ByteArrayOutputStream output = null;
byte [] xml = null;
byte [] buffer = new byte[8192];
int bytesRead;
try {
// Basic deduplication
FormDefinitionRepo formDefinitionRepo = new FormDefinitionRepo(Collect.getInstance().getDbService().getDb(copyToFolderId));
List<FormDefinition> definitions = formDefinitionRepo.findByName(copyFormAsName);
if (!definitions.isEmpty()) {
duplicate = true;
return null;
}
ais = Collect.getInstance().getDbService().getDb().getAttachment(formDefinition.getId(), "xml");
FormDefinition copyOfFormDefinition = new FormDefinition();
// If copying with the exact same name
if (copyFormAsName.equals(formDefinition.getName())) {
output = new ByteArrayOutputStream();
while ((bytesRead = ais.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
xml = output.toByteArray();
output.close();
// No need to recompute this if it is an exact copy
copyOfFormDefinition.setXmlHash(formDefinition.getXmlHash());
} else {
// Rename form definition
xml = renameFormDefinition(ais, copyFormAsName);
// Save to file first so we can get md5 hash
File f = new File(FileUtilsExtended.EXTERNAL_CACHE + File.separator + UUID.randomUUID() + ".xml");
FileOutputStream fos = new FileOutputStream(f);
fos.write(xml);
fos.close();
copyOfFormDefinition.setXmlHash(FileUtils.getMd5Hash(f));
f.delete();
}
ais.close();
copyOfFormDefinition.setName(copyFormAsName);
copyOfFormDefinition.addInlineAttachment(new Attachment("xml", new String(Base64Coder.encode(xml)).toString(), FormWriter.CONTENT_TYPE));
Collect.getInstance().getDbService().getDb(copyToFolderId).create(copyOfFormDefinition);
// Copy all remaining attachments from the original form definition; preserve names
if (formDefinition.getAttachments().size() > 1) {
String formCachePath = FileUtilsExtended.FORMS_PATH + File.separator + formDefinition.getId();
String formCacheMediaPath = formCachePath + File.separator + FileUtilsExtended.MEDIA_DIR;
FileUtils.createFolder(formCachePath);
FileUtils.createFolder(formCacheMediaPath);
// Download attachments
for (Entry<String, Attachment> entry : formDefinition.getAttachments().entrySet()) {
ais = Collect.getInstance().getDbService().getDb().getAttachment(formDefinition.getId(), entry.getKey());
FileOutputStream file;
if (!entry.getKey().equals("xml")) {
file = new FileOutputStream(formCacheMediaPath + File.separator + entry.getKey());
buffer = new byte[8192];
bytesRead = 0;
while ((bytesRead = ais.read(buffer)) != -1) {
file.write(buffer, 0, bytesRead);
}
file.close();
}
ais.close();
}
// Upload to new form definition document
String revision = copyOfFormDefinition.getRevision();
for (File f : new File(formCacheMediaPath).listFiles()) {
String fileName = f.getName();
String attachmentName = fileName;
if (Collect.Log.VERBOSE) Log.v(Collect.LOGTAG, t + ": attaching " + fileName + " to " + copyOfFormDefinition.getId());
String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1);
String contentType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension);
FileInputStream fis = new FileInputStream(f);
ais = new AttachmentInputStream(attachmentName, fis, contentType, f.length());
revision = Collect.getInstance().getDbService().getDb(copyToFolderId).createAttachment(copyOfFormDefinition.getId(), revision, ais);
fis.close();
ais.close();
}
}
copied = true;
} catch (Exception e) {
if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, tt + "unexpected exception");
e.printStackTrace();
}
return null;
}
@Override
protected void onPreExecute()
{
progressDialog = new ProgressDialog(BrowserActivity.this);
progressDialog.setMessage(getString(R.string.tf_copying_please_wait));
progressDialog.show();
}
@Override
protected void onPostExecute(Void nothing)
{
try {
progressDialog.dismiss();
progressDialog = null;
} catch (Exception e) {
// Do nothing if view is no longer attached
}
if (copied) {
Toast.makeText(getApplicationContext(), getString(R.string.tf_something_was_successful, getString(R.string.tf_copy)), Toast.LENGTH_SHORT).show();
if (copyToFolderId.equals(Collect.getInstance().getInformOnlineState().getSelectedDatabase()))
loadScreen();
} else if (duplicate) {
// Show duplicate explanation dialog
showDialog(DIALOG_UNABLE_TO_COPY_DUPLICATE);
} else {
// Some other failure
Toast.makeText(getApplicationContext(), getString(R.string.tf_something_failed, getString(R.string.tf_copy)), Toast.LENGTH_LONG).show();
}
}
}
/*
* Create a new form definition and launch the built-in form editor
*/
private class CreateFormDefinitionTask extends AsyncTask<Object, Void, Void>
{
private boolean isDuplicate = false;
private boolean isSuccessful = true;
private FormDefinition f;
private ProgressDialog progressDialog = null;
@Override
protected Void doInBackground(Object... params)
{
f = (FormDefinition) params[0];
try {
// Basic deduplication
FormDefinitionRepo repo = new FormDefinitionRepo(Collect.getInstance().getDbService().getDb(Collect.getInstance().getInformOnlineState().getSelectedDatabase()));
List<FormDefinition> definitions = repo.findByName(f.getName());
if (!definitions.isEmpty()) {
isDuplicate = true;
return null;
}
// Create empty form from template
InputStream is = getResources().openRawResource(R.raw.xform_template);
// Set up variables to receive data
ByteArrayOutputStream data = new ByteArrayOutputStream();
byte[] inputbuf = new byte[8192];
int inputlen;
while ((inputlen = is.read(inputbuf)) > 0) {
data.write(inputbuf, 0, inputlen);
}
// Add initial XForm template
f.addInlineAttachment(new Attachment("xml", new String(Base64Coder.encode(data.toByteArray())).toString(), FormWriter.CONTENT_TYPE));
// Create form definition
Collect.getInstance().getDbService().getDb().create(f);
is.close();
data.close();
} catch (Exception e) {
if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "unable to read XForm template file; create new form process will fail");
e.printStackTrace();
isSuccessful = false;
}
return null;
}
@Override
protected void onPreExecute()
{
progressDialog = new ProgressDialog(BrowserActivity.this);
progressDialog.setMessage(getString(R.string.tf_creating_template_please_wait));
progressDialog.show();
}
@Override
protected void onPostExecute(Void nothing)
{
try {
progressDialog.dismiss();
progressDialog = null;
} catch (Exception e) {
// Do nothing if view is no longer attached
}
if (isDuplicate) {
Toast.makeText(getApplicationContext(), getString(R.string.tf_form_name_duplicate), Toast.LENGTH_LONG).show();
showDialog(DIALOG_CREATE_TEMPLATE);
} else {
if (isSuccessful) {
Intent i = new Intent(BrowserActivity.this, FieldList.class);
i.putExtra(FormEntryActivity.KEY_FORMPATH, f.getId());
startActivity(i);
} else {
mDialogMessage = getString(R.string.tf_unable_to_open_folder, getSelectedFolderName());
showDialog(DIALOG_FOLDER_UNAVAILABLE);
}
}
}
}
/*
* Determine whether it is safe to launch the form browser. For the time
* being we need this so that we can allow/disallow access based on whether
* instances exist for a given form.
*/
private class FormBuilderLauncherTask extends AsyncTask<String, Void, String>
{
@Override
protected String doInBackground(String... arg0)
{
String docId = arg0[0];
List<FormInstance> instanceIds = new ArrayList<FormInstance>();
String result = "";
try {
instanceIds = new FormInstanceRepo(Collect.getInstance().getDbService().getDb()).findByFormId(docId);
} catch (Exception e) {
if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "unexpected exception " + e.toString());
e.printStackTrace();
} finally {
if (instanceIds.isEmpty())
result = docId;
}
return result;
}
@Override
protected void onPreExecute()
{
setProgressVisibility(true);
}
@Override
protected void onPostExecute(String docId)
{
if (docId.length() > 0) {
// Success
Intent i = new Intent(BrowserActivity.this, FieldList.class);
i.putExtra(FormEntryActivity.KEY_FORMPATH, docId);
startActivity(i);
} else {
// Failure (instances present)
showDialog(DIALOG_FORM_BUILDER_LAUNCH_ERROR);
}
setProgressVisibility(false);
}
}
/*
* Retrieve all instances of a certain status for a specified definition,
* populate the instance browse list and start FormEditActivity accordingly.
*/
private class InstanceLoadPathTask extends AsyncTask<Object, Integer, Void>
{
String formId;
ArrayList<String> instanceIds = new ArrayList<String>();
boolean caughtExceptionInBackground = true;
@Override
protected Void doInBackground(Object... params)
{
try {
formId = (String) params[0];
FormInstance.Status status = (FormInstance.Status) params[1];
instanceIds = new FormInstanceRepo(Collect.getInstance().getDbService().getDb()).findByFormAndStatus(formId, status);
caughtExceptionInBackground = false;
} catch (Exception e) {
if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "unhandled exception while processing InstanceLoadPathTask.doInBackground(): " + e.toString());
e.printStackTrace();
}
return null;
}
@Override
protected void onPreExecute()
{
setProgressVisibility(true);
}
@Override
protected void onPostExecute(Void nothing)
{
if (caughtExceptionInBackground) {
mDialogMessage = getString(R.string.tf_unable_to_open_folder, getSelectedFolderName());
showDialog(DIALOG_FOLDER_UNAVAILABLE);
} else {
try {
Intent i = new Intent(BrowserActivity.this, FormEntryActivity.class);
i.putStringArrayListExtra(FormEntryActivity.KEY_INSTANCES, instanceIds);
i.putExtra(FormEntryActivity.KEY_INSTANCEPATH, instanceIds.get(0));
i.putExtra(FormEntryActivity.KEY_FORMPATH, formId);
startActivity(i);
} catch (IndexOutOfBoundsException e) {
// There were no mInstanceIds returned (no DB error, per-se but something was missing)
showDialog(DIALOG_INSTANCES_UNAVAILABLE);
}
}
setProgressVisibility(false);
}
}
/*
* Refresh the main form browser view as requested by the user
*/
private class RefreshViewTask extends AsyncTask<Void, Integer, Void>
{
private HashMap<String, HashMap<String, String>> tallies = new HashMap<String, HashMap<String, String>>();
private ArrayList<FormDefinition> definitions = new ArrayList<FormDefinition>();
private ArrayList<FormInstance> instances = new ArrayList<FormInstance>();
private FormInstance.Status statusFilter = null;
private Bundle filterOptions = null;
private boolean folderOutdated = false;
private boolean folderUnavailable = false;
@Override
protected Void doInBackground(Void... params)
{
try {
if (filterOptions == null && statusFilter == null) {
// Search results pulled but nothing to show
} else if (filterOptions == null) {
// No search options, we must be using the simple status filter
// TODO: move to AccountFolderList and activate when a user opens a folder?
Collect.getInstance().getDbService().performHousekeeping(Collect.getInstance().getInformOnlineState().getSelectedDatabase());
FormDefinitionRepo repo = new FormDefinitionRepo(Collect.getInstance().getDbService().getDb());
tallies = repo.getFormsByInstanceStatus(statusFilter);
if (statusFilter.equals(FormInstance.Status.any)) {
definitions = (ArrayList<FormDefinition>) repo.getAllActive();
} else {
definitions = (ArrayList<FormDefinition>) repo.getAllActiveByKeys(new ArrayList<Object>(tallies.keySet()));
}
DocumentUtils.sortDefinitionsByName(definitions);
} else {
// Use search filter options
FormDefinitionRepo definitionRepo = new FormDefinitionRepo(Collect.getInstance().getDbService().getDb());
definitions = (ArrayList<FormDefinition>) definitionRepo.getAll();
List<String> assignmentParameter = new ArrayList<String>();
FormInstance.Status statusParameter = FormInstance.Status.any;
switch (filterOptions.getInt(KEY_SEARCH_BY_ASSIGNMENT, 0)) {
case 0:
// Any device
break;
case 1:
// This device
assignmentParameter.add(Collect.getInstance().getInformOnlineState().getDeviceId());
break;
case 2:
// Specific devices
assignmentParameter = filterOptions.getStringArrayList(KEY_SEARCH_BY_ASSIGNMENT_IDS);
break;
}
switch (filterOptions.getInt(KEY_SEARCH_BY_STATUS, 0)) {
case 0:
// Any status
break;
case 1:
// Complete only
statusParameter = FormInstance.Status.complete;
break;
case 2:
// Draft only
statusParameter = FormInstance.Status.draft;
break;
}
FormInstanceRepo instanceRepo = new FormInstanceRepo(Collect.getInstance().getDbService().getDb());
instances = (ArrayList<FormInstance>) instanceRepo.findByFilterIndex(assignmentParameter, statusParameter);
DocumentUtils.sortByDateCreated(instances);
}
} catch (ClassCastException e) {
// TODO: is there a better way to handle empty lists?
} catch (DocumentNotFoundException e) {
/*
* This most likely cause of this exception is that a design document could not be found. This will happen if we are
* running a version of Inform that expects a design document by a certain name but the local folder does not have
* the most recent design documents.
*/
if (Collect.Log.WARN) Log.w(Collect.LOGTAG, t + e.toString());
folderOutdated = true;
folderUnavailable = true;
} catch (Exception e) {
if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "unexpected exception " + e.toString());
e.printStackTrace();
folderUnavailable = true;
}
return null;
}
@Override
protected void onPreExecute()
{
setProgressVisibility(true);
((Spinner) findViewById(R.id.taskSpinner)).setClickable(false);
((Spinner) findViewById(R.id.taskSpinner)).setEnabled(false);
}
@Override
protected void onPostExecute(Void nothing)
{
boolean noResults = true;
/*
* Special hack to ensure that our application doesn't crash if we terminate it
* before the AsyncTask has finished running. This is stupid and I don't know
* another way around it.
*
* See http://dimitar.me/android-displaying-dialogs-from-background-threads/
*/
if (isFinishing())
return;
// Stop progress
RelativeLayout onscreenProgress = (RelativeLayout) findViewById(R.id.progress);
onscreenProgress.setVisibility(View.GONE);
// Re-enable task selector
((Spinner) findViewById(R.id.taskSpinner)).setClickable(true);
((Spinner) findViewById(R.id.taskSpinner)).setEnabled(true);
if (folderUnavailable) {
String db = Collect.getInstance().getInformOnlineState().getSelectedDatabase();
boolean isReplicated = Collect.getInstance().getInformOnlineState().getAccountFolders().get(db).isReplicated();
if (folderOutdated && isReplicated) {
showDialog(DIALOG_FOLDER_OUTDATED);
} else {
mDialogMessage = getString(R.string.tf_unable_to_open_folder, getSelectedFolderName());
showDialog(DIALOG_FOLDER_UNAVAILABLE);
}
} else {
if (filterOptions == null) {
noResults = definitions.isEmpty();
BrowserShortListAdapter adapter = new BrowserShortListAdapter(BrowserActivity.this, R.layout.browser_list_item, definitions, tallies, (Spinner) findViewById(R.id.taskSpinner));
setListAdapter(adapter);
} else {
noResults = instances.isEmpty();
BrowserLongListAdapter adapter = new BrowserLongListAdapter(BrowserActivity.this, R.layout.browser_list_item, instances, definitions);
setListAdapter(adapter);
}
if (noResults) {
TextView nothingToDisplay = (TextView) findViewById(R.id.nothingToDisplay);
nothingToDisplay.setVisibility(View.VISIBLE);
}
}
setProgressVisibility(false);
}
// Simplistic filtering
public void setFilterByStatus(FormInstance.Status s)
{
statusFilter = s;
}
// Search filters for displaying forms via the long adapter
public void setFilterOptions(Bundle b)
{
filterOptions = b;
}
}
private class RemoveDefinitionTask extends AsyncTask<Object, Void, Void>
{
FormDefinition formDefinition;
ProgressDialog progressDialog;
boolean removed = false;
@Override
protected Void doInBackground(Object... params)
{
formDefinition = (FormDefinition) params[0];
formDefinition.setStatus(FormDefinition.Status.removed);
try {
Collect.getInstance().getDbService().getDb().update(formDefinition);
removed = true;
} catch (Exception e) {
Log.e(Collect.LOGTAG, t + "unexpected exception");
e.printStackTrace();
}
return null;
}
@Override
protected void onPreExecute()
{
progressDialog = new ProgressDialog(BrowserActivity.this);
progressDialog.setMessage(getString(R.string.tf_removing_please_wait));
progressDialog.show();
}
@Override
protected void onPostExecute(Void nothing)
{
try {
progressDialog.dismiss();
progressDialog = null;
} catch (Exception e) {
// Do nothing if view is no longer attached
}
// TODO
if (removed) {
Toast.makeText(getApplicationContext(), getString(R.string.tf_something_was_successful, getString(R.string.tf_removal)), Toast.LENGTH_SHORT).show();
} else {
// Unspecified failure
Toast.makeText(getApplicationContext(), getString(R.string.tf_something_failed, getString(R.string.tf_removal)), Toast.LENGTH_LONG).show();
}
loadScreen();
}
}
private class RenameDefinitionTask extends AsyncTask<Object, Void, Void>
{
private static final String tt = t + "RenameDefinitionTask: ";
private static final String KEY_ITEM = "key_item";
private boolean renamed = false;
private boolean duplicate = false;
private String newName;
private FormDefinition formDefinition;
ProgressDialog progressDialog = null;
final Handler progressHandler = new Handler() {
public void handleMessage(Message msg) {
progressDialog.setMessage(getString(R.string.tf_renaming_with_param, msg.getData().getString(KEY_ITEM)));
}
};
@Override
protected Void doInBackground(Object... params)
{
formDefinition = (FormDefinition) params[0];
newName = (String) params[1];
Log.d(Collect.LOGTAG, tt + "about to rename " + formDefinition.getId() + " to " + newName);
Message msg = progressHandler.obtainMessage();
Bundle b = new Bundle();
b.putString(KEY_ITEM, formDefinition.getName());
msg.setData(b);
progressHandler.sendMessage(msg);
AttachmentInputStream ais = null;
byte [] xml = null;
try {
// Basic deduplication
FormDefinitionRepo formDefinitionRepo = new FormDefinitionRepo(Collect.getInstance().getDbService().getDb());
List<FormDefinition> definitions = formDefinitionRepo.findByName(newName);
if (!definitions.isEmpty()) {
// If there is more than one match OR the first (and only) match isn't the form that was selected
if (definitions.size() > 1 || definitions.get(0).getId() != formDefinition.getId()) {
duplicate = true;
return null;
}
}
ais = Collect.getInstance().getDbService().getDb().getAttachment(formDefinition.getId(), "xml");
// Rename form definition
xml = renameFormDefinition(ais, newName);
// Save to file first so we can get md5 hash
File f = new File(FileUtilsExtended.EXTERNAL_CACHE + File.separator + UUID.randomUUID() + ".xml");
FileOutputStream fos = new FileOutputStream(f);
fos.write(xml);
fos.close();
formDefinition.setXmlHash(FileUtils.getMd5Hash(f));
f.delete();
ais.close();
formDefinition.setName(newName);
formDefinition.addInlineAttachment(new Attachment("xml", new String(Base64Coder.encode(xml)).toString(), FormWriter.CONTENT_TYPE));
Collect.getInstance().getDbService().getDb().update(formDefinition);
renamed = true;
} catch (Exception e) {
if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, tt + "unexpected exception");
e.printStackTrace();
}
return null;
}
@Override
protected void onPreExecute()
{
progressDialog = new ProgressDialog(BrowserActivity.this);
progressDialog.setMessage(getString(R.string.tf_renaming_please_wait));
progressDialog.show();
}
@Override
protected void onPostExecute(Void nothing)
{
try {
progressDialog.dismiss();
progressDialog = null;
} catch (Exception e) {
// Do nothing if view is no longer attached
}
if (renamed) {
Toast.makeText(getApplicationContext(), getString(R.string.tf_something_was_successful, getString(R.string.tf_rename)), Toast.LENGTH_SHORT).show();
} else if (duplicate) {
// Show duplicate explanation dialog
showDialog(DIALOG_UNABLE_TO_RENAME_DUPLICATE);
} else {
// Some other failure
Toast.makeText(getApplicationContext(), getString(R.string.tf_something_failed, getString(R.string.tf_rename)), Toast.LENGTH_LONG).show();
}
loadScreen();
}
}
/*
* Update (synchronize) a local database by pulling from the remote database.
* Needed if the local database becomes outdated.
*/
private class UpdateFolderTask extends AsyncTask<Void, Void, Void>
{
String db = Collect.getInstance().getInformOnlineState().getSelectedDatabase();
AccountFolder folder = Collect.getInstance().getInformOnlineState().getAccountFolders().get(db);
ReplicationStatus status = null;
@Override
protected Void doInBackground(Void... nothing)
{
try {
status = Collect.getInstance().getDbService().replicate(folder.getId(), DatabaseService.REPLICATE_PULL);
} catch (Exception e) {
if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "unable to replicate during UpdateFolderTask: " + e.toString());
e.printStackTrace();
status = null;
}
return null;
}
@Override
protected void onPreExecute()
{
showDialog(DIALOG_UPDATING_FOLDER);
}
@Override
protected void onPostExecute(Void nothing)
{
removeDialog(DIALOG_UPDATING_FOLDER);
// No changes is the same as "unable to update" because chances are it will lead to the same problem
if (status == null || !status.isOk() || status.isNoChanges())
Toast.makeText(getApplicationContext(), getString(R.string.tf_unable_to_update_folder, getSelectedFolderName()), Toast.LENGTH_LONG).show();
else
Toast.makeText(getApplicationContext(), getString(R.string.tf_folder_updated, getSelectedFolderName()), Toast.LENGTH_SHORT).show();
loadScreen();
}
}
// Attempt to return the current folder name (shortened to an appropriate length)
public static String getSelectedFolderName()
{
String folderName = "...";
try {
folderName = Collect
.getInstance()
.getInformOnlineState()
.getAccountFolders()
.get(Collect.getInstance().getInformOnlineState().getSelectedDatabase())
.getName();
// Shorten names that are too long
if (folderName.length() > 23)
folderName = folderName.substring(0, 20) + "...";
} catch (NullPointerException e) {
// Database metadata is not available at this time
Log.w(Collect.LOGTAG, t + "folder metadata not available at this time");
folderName = "?";
}
return folderName;
}
@Override
public void importTaskFinished(Bundle data)
{
dismissDialog(DIALOG_IMPORTING_TEMPLATE);
if (data.getBoolean(DefinitionImportListener.SUCCESSFUL, false)) {
Toast.makeText(getApplicationContext(), getString(R.string.tf_imported_file, data.getString(DefinitionImportListener.FILENAME)), Toast.LENGTH_SHORT).show();
loadScreen();
} else {
mDialogMessage = data.getString(DefinitionImportListener.MESSAGE);
showDialog(DIALOG_UNABLE_TO_IMPORT_TEMPLATE);
}
loadScreen();
}
@Override
public void synchronizationHandler(Message msg)
{
if (msg == null) {
// Close any existing progress dialogs
if (mProgressDialog != null && mProgressDialog.isShowing())
mProgressDialog.dismiss();
// Start new dialog with suitable message
mProgressDialog = new ProgressDialog(BrowserActivity.this);
mProgressDialog.setMessage(getString(R.string.tf_synchronizing_folders_dialog_msg));
mProgressDialog.show();
} else {
// Update progress dialog
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.setMessage(getString(R.string.tf_synchronizing_folder_count_dialog_msg, msg.arg1, msg.arg2));
}
}
}
@Override
public void synchronizationTaskFinished(Bundle data)
{
if (mProgressDialog != null && mProgressDialog.isShowing())
mProgressDialog.dismiss();
if (data.getBoolean(SynchronizeFoldersListener.POS, false)) {
// Alternate post-synchronization workflow (go offline after synchronizing)
mToggleOnlineStateTask = new ToggleOnlineStateTask();
mToggleOnlineStateTask.setListener(BrowserActivity.this);
mToggleOnlineStateTask.execute();
} else {
// Refresh after synchronizing to reflect any changes
loadScreen();
}
}
@Override
public void toggleOnlineStateHandler()
{
showDialog(DIALOG_ONLINE_STATE_CHANGING);
// Not available while toggling
Button b1 = (Button) findViewById(R.id.onlineStatusTitleButton);
b1.setEnabled(false);
b1.setText(R.string.tf_inform_state_transition);
Button b2 = (Button) findViewById(R.id.folderTitleButton);
b2.setEnabled(false);
b2.setText("...");
}
@Override
public void toggleOnlineStateTaskFinished(Bundle data)
{
removeDialog(DIALOG_ONLINE_STATE_CHANGING);
switch (data.getInt(ToggleOnlineStateListener.OUTCOME)) {
case ToggleOnlineStateListener.SUCCESSFUL:
// If we are signed in after toggling then it makes sense that we'd want to synchronize (but only if requested)
if (Collect.getInstance().getIoService().isSignedIn() && data.getBoolean(ToggleOnlineStateListener.POS, false)) {
mSynchronizeFoldersTask = new SynchronizeFoldersTask();
mSynchronizeFoldersTask.setListener(BrowserActivity.this);
mSynchronizeFoldersTask.setTransferMode(SynchronizeFoldersListener.MODE_SWAP);
mSynchronizeFoldersTask.execute();
} else {
loadScreen();
}
break;
case ToggleOnlineStateListener.CANNOT_SIGNIN:
// Load screen after user acknowledges to avoid stacking of dialogs
showDialog(DIALOG_ONLINE_ATTEMPT_FAILED);
break;
case ToggleOnlineStateListener.CANNOT_SIGNOUT:
// Load screen after user acknowledges to avoid stacking of dialogs
showDialog(DIALOG_OFFLINE_ATTEMPT_FAILED);
break;
}
}
/*
* Load the various elements of the screen that must wait for other tasks to complete
*/
private void loadScreen()
{
String folderName = "?";
try {
// Reflect the online/offline status (may be disabled thanks to toggling state)
Button b1 = (Button) findViewById(R.id.onlineStatusTitleButton);
b1.setEnabled(true);
if (Collect.getInstance().getIoService().isSignedIn()) {
b1.setText(getText(R.string.tf_inform_state_online));
} else {
if (Collect.getInstance().getInformOnlineState().isOfflineModeEnabled())
b1.setText(getText(R.string.tf_inform_state_offline));
else
b1.setText(getText(R.string.tf_inform_state_disconnected));
}
// Hide "nothing to display" message
TextView nothingToDisplay = (TextView) findViewById(R.id.nothingToDisplay);
nothingToDisplay.setVisibility(View.INVISIBLE);
// Restore selected database (but only once)
if (mSelectedDatabase != null) {
Log.v(Collect.LOGTAG, t + "restoring selected database " + mSelectedDatabase);
Collect.getInstance().getInformOnlineState().setSelectedDatabase(mSelectedDatabase);
mSelectedDatabase = null;
}
folderName = getSelectedFolderName();
// Re-enable and display currently selected folder (may be disabled thanks to toggling state)
Button b2 = (Button) findViewById(R.id.folderTitleButton);
b2.setEnabled(true);
b2.setText(folderName);
// Open selected database
Collect.getInstance().getDbService().open(Collect.getInstance().getInformOnlineState().getSelectedDatabase());
mRefreshViewTask = new RefreshViewTask();
// Spinner must reflect results of refresh view below
switch (((Spinner) findViewById(R.id.taskSpinner)).getSelectedItemPosition()) {
case 0:
Log.v("DEBUG", "refresh 0");
// Show all templates to start new form
mRefreshViewTask.setFilterByStatus(FormInstance.Status.any);
break;
case 1:
Log.v("DEBUG", "refresh 1");
// Show templates with complete forms
mRefreshViewTask.setFilterByStatus(FormInstance.Status.complete);
break;
case 2:
Log.v("DEBUG", "refresh 2");
// Show templates with draft forms
mRefreshViewTask.setFilterByStatus(FormInstance.Status.draft);
break;
case 3:
Log.v("DEBUG", "refresh 3");
// Show forms according to filter, search results
mRefreshViewTask.setFilterOptions(mSearchFilter);
mRefreshViewTask.setFilterByStatus(null);
break;
case 4:
Log.v("DEBUG", "refresh 4");
// Show all templates (for record export)
case 5:
Log.v("DEBUG", "refresh 5");
// Show all templates (to edit template)
mRefreshViewTask.setFilterByStatus(FormInstance.Status.any);
break;
}
mRefreshViewTask.execute();
registerForContextMenu(getListView());
} catch (DatabaseService.DbUnavailableDueToMetadataException e) {
mDialogMessage = getString(R.string.tf_unable_to_open_folder_missing_metadata);
showDialog(DIALOG_FOLDER_UNAVAILABLE);
} catch (DatabaseService.DbUnavailableWhileOfflineException e) {
mDialogMessage = getString(R.string.tf_unable_to_open_folder_while_offline, folderName);
showDialog(DIALOG_FOLDER_UNAVAILABLE);
} catch (DatabaseService.DbUnavailableException e) {
mDialogMessage = getString(R.string.tf_unable_to_open_folder, folderName);
showDialog(DIALOG_FOLDER_UNAVAILABLE);
} catch (NullPointerException e) {
// Something failed to return -- restart app
// FIXME: remove this workaround -- figure out why it happens in the first place
Intent exit = new Intent();
exit.putExtra("exit_app", true);
setResult(RESULT_OK, exit);
finish();
Intent i = getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
}
}
/*
* Parse an attachment input stream (form definition XML file), affect h:title and instance
* root & id attribute and return the XML file as byte[] for consumption by the controlling task.
*/
private byte[] renameFormDefinition(AttachmentInputStream ais, String newName) throws Exception
{
FormReader fr = new FormReader(ais, false);
// Populate global state (expected by FormWriter)
Collect.getInstance().getFormBuilderState().setBinds(fr.getBinds());
Collect.getInstance().getFormBuilderState().setFields(fr.getFields());
Collect.getInstance().getFormBuilderState().setInstance(fr.getInstance());
Collect.getInstance().getFormBuilderState().setTranslations(fr.getTranslations());
return FormWriter.writeXml(newName, fr.getInstanceRoot(), fr.getInstanceRootId());
}
// Toggle progress spinner in custom title bar
private void setProgressVisibility(boolean visible)
{
ProgressBar pb = (ProgressBar) getWindow().findViewById(R.id.titleProgressBar);
if (pb != null) {
if (visible) {
pb.setVisibility(View.VISIBLE);
} else {
pb.setVisibility(View.GONE);
}
}
}
}