Java tutorial
/* * Copyright (C) 2012-2013 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.opendatakit.survey.android.fragments; import java.net.URI; import java.util.ArrayList; import java.util.Iterator; import java.util.Set; import org.apache.commons.lang3.StringEscapeUtils; import org.opendatakit.common.android.provider.InstanceColumns; import org.opendatakit.common.android.utilities.ODKDatabaseUtils; import org.opendatakit.common.android.utilities.WebLogger; import org.opendatakit.survey.android.R; import org.opendatakit.survey.android.activities.ODKActivity; import org.opendatakit.survey.android.fragments.AlertDialogFragment.ConfirmAlertDialog; import org.opendatakit.survey.android.fragments.ProgressDialogFragment.CancelProgressDialog; import org.opendatakit.survey.android.listeners.InstanceUploaderListener; import org.opendatakit.survey.android.logic.InstanceUploadOutcome; import org.opendatakit.survey.android.provider.InstanceProviderAPI; import android.app.AlertDialog; import android.app.Fragment; import android.app.FragmentManager; import android.app.ListFragment; import android.app.LoaderManager; import android.content.Context; import android.content.CursorLoader; import android.content.DialogInterface; import android.content.Loader; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import android.widget.Toast; /** * List view of the instances of the current form that are finalized and can be * uploaded. * * @author mitchellsundt@gmail.com * */ public class InstanceUploaderListFragment extends ListFragment implements OnLongClickListener, InstanceUploaderListener, ConfirmAlertDialog, CancelProgressDialog, LoaderManager.LoaderCallbacks<Cursor> { private static final String PROGRESS_DIALOG_TAG = "progressDialog"; private static final String t = "InstanceUploaderListFragment"; private static final int INSTANCE_UPLOADER_LIST_LOADER = 0x03; public static final int ID = R.layout.instance_uploader_list; private static enum DialogState { Progress, Alert, None }; // keys for the data being retained private static final String BUNDLE_SELECTED_ITEMS_KEY = "selected_items"; private static final String BUNDLE_TOGGLED_KEY = "toggled"; private static final String DIALOG_TITLE = "dialogtitle"; private static final String DIALOG_MSG = "dialogmsg"; private static final String DIALOG_STATE = "dialogState"; private static final String SHOW_UNSENT = "showUnsent"; private static final String URL = "url"; // data to retain across orientation changes private ArrayList<String> mSelected = new ArrayList<String>(); private boolean mToggled = false; private String mAlertTitle; private String mAlertMsg; private DialogState mDialogState = DialogState.None; private boolean mShowUnsent = true; private URI mUrl = null; // data that is not retained private Button mUploadButton; private Button mToggleButton; private SimpleCursorAdapter mInstances; private View view; private Handler handler = new Handler(); private ProgressDialogFragment progressDialog = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); String[] data = new String[] { InstanceColumns.DISPLAY_NAME, InstanceColumns.DISPLAY_SUBTEXT, InstanceColumns._ID }; int[] view = new int[] { R.id.text1, R.id.text2 }; // render total instance view mInstances = new SimpleCursorAdapter(getActivity(), R.layout.upload_multiple_choice, null, data, view, 0); setListAdapter(mInstances); getLoaderManager().initLoader(INSTANCE_UPLOADER_LIST_LOADER, null, this); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { view = inflater.inflate(ID, container, false); // set up long click listener mUploadButton = (Button) view.findViewById(R.id.upload_button); mUploadButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { ConnectivityManager connectivityManager = (ConnectivityManager) getActivity() .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo ni = connectivityManager.getActiveNetworkInfo(); if (ni == null || !ni.isConnected()) { Toast.makeText(getActivity(), R.string.no_connection, Toast.LENGTH_SHORT).show(); } else { // items selected uploadSelectedFiles(); mToggled = false; mSelected.clear(); clearChoices(); } } }); mToggleButton = (Button) view.findViewById(R.id.toggle_button); mToggleButton.setLongClickable(true); mToggleButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // toggle selections of items to all or none ListView ls = getListView(); mToggled = !mToggled; // remove all items from selected list mSelected.clear(); for (int pos = 0; pos < ls.getCount(); pos++) { ls.setItemChecked(pos, mToggled); // add all items if mToggled sets to select all if (mToggled) { Cursor c = (Cursor) ls.getItemAtPosition(pos); String uuid = ODKDatabaseUtils.get().getIndexAsString(c, c.getColumnIndex(InstanceColumns._ID)); mSelected.add(uuid); } } mUploadButton.setEnabled(!(mSelected.size() == 0)); } }); mToggleButton.setOnLongClickListener(this); if (savedInstanceState != null) { String[] selectedArray = savedInstanceState.getStringArray(BUNDLE_SELECTED_ITEMS_KEY); for (int i = 0; i < selectedArray.length; i++) mSelected.add(selectedArray[i]); // indicating whether or not select-all is on or off. if (savedInstanceState.containsKey(BUNDLE_TOGGLED_KEY)) { mToggled = savedInstanceState.getBoolean(BUNDLE_TOGGLED_KEY); } // to restore alert dialog. if (savedInstanceState.containsKey(DIALOG_TITLE)) { mAlertTitle = savedInstanceState.getString(DIALOG_TITLE); } if (savedInstanceState.containsKey(DIALOG_MSG)) { mAlertMsg = savedInstanceState.getString(DIALOG_MSG); } if (savedInstanceState.containsKey(DIALOG_STATE)) { mDialogState = DialogState.valueOf(savedInstanceState.getString(DIALOG_STATE)); } if (savedInstanceState.containsKey(SHOW_UNSENT)) { mShowUnsent = savedInstanceState.getBoolean(SHOW_UNSENT); } if (savedInstanceState.containsKey(URL)) { mUrl = URI.create(savedInstanceState.getString(URL)); } } mUploadButton.setEnabled(!(mSelected.size() == 0)); return view; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); String[] selectedArray = new String[mSelected.size()]; for (int i = 0; i < mSelected.size(); i++) selectedArray[i] = mSelected.get(i); outState.putStringArray(BUNDLE_SELECTED_ITEMS_KEY, selectedArray); outState.putBoolean(BUNDLE_TOGGLED_KEY, mToggled); outState.putString(DIALOG_TITLE, mAlertTitle); outState.putString(DIALOG_MSG, mAlertMsg); outState.putString(DIALOG_STATE, mDialogState.name()); outState.putBoolean(SHOW_UNSENT, mShowUnsent); if (mUrl != null) { outState.putString(URL, mUrl.toString()); } } private void clearChoices() { getListView().clearChoices(); mUploadButton.setEnabled(false); } public void changeUploadTableId() { if (getActivity() != null) { // if we are already attached to an activity, restart the loader. // otherwise, we will eventually be attached. getLoaderManager().restartLoader(INSTANCE_UPLOADER_LIST_LOADER, null, this); } } @Override public void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); // get row id from db Cursor c = (Cursor) getListAdapter().getItem(position); String k = ODKDatabaseUtils.get().getIndexAsString(c, c.getColumnIndex(InstanceColumns._ID)); // add/remove from selected list if (mSelected.contains(k)) mSelected.remove(k); else mSelected.add(k); mUploadButton.setEnabled(!(mSelected.size() == 0)); } private void uploadSelectedFiles() { if (mSelected.size() > 0 && ((ODKActivity) getActivity()).getUploadTableId() != null) { // show dialog box showProgressDialog(); BackgroundTaskFragment f = (BackgroundTaskFragment) getFragmentManager() .findFragmentByTag("background"); String[] str = new String[mSelected.size()]; f.uploadInstances(this, ((ODKActivity) getActivity()).getAppName(), ((ODKActivity) getActivity()).getUploadTableId(), mSelected.toArray(str)); } } @Override public void onResume() { super.onResume(); getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); getListView().setItemsCanFocus(false); // if current activity is being reinitialized due to changing // orientation restore all checkmarks for ones selected ListView ls = getListView(); for (String id : mSelected) { for (int pos = 0; pos < ls.getCount(); pos++) { Cursor c = (Cursor) ls.getItemAtPosition(pos); String uuid = ODKDatabaseUtils.get().getIndexAsString(c, c.getColumnIndex(InstanceColumns._ID)); if (id.equals(uuid)) { ls.setItemChecked(pos, true); break; } } } FragmentManager mgr = getFragmentManager(); BackgroundTaskFragment f = (BackgroundTaskFragment) mgr.findFragmentByTag("background"); f.establishInstanceUploaderListener(this); if (mDialogState == DialogState.Progress) { restoreProgressDialog(); } else if (mDialogState == DialogState.Alert) { restoreAlertDialog(); } } @Override public void onPause() { FragmentManager mgr = getFragmentManager(); // dismiss dialogs... AlertDialogFragment alertDialog = (AlertDialogFragment) mgr.findFragmentByTag("alertDialog"); if (alertDialog != null) { alertDialog.dismiss(); } dismissProgressDialog(); super.onPause(); } private void showAuthDialog() { AuthDialogFragment f = AuthDialogFragment.newInstance(getId(), getString(R.string.server_requires_auth), getString(R.string.server_auth_credentials, mUrl.toString()), mUrl.toString()); f.show(getFragmentManager(), "authDialog"); } private void restoreProgressDialog() { Fragment alert = getFragmentManager().findFragmentByTag("alertDialog"); if (alert != null) { ((AlertDialogFragment) alert).dismiss(); } if (mDialogState == DialogState.Progress) { Fragment dialog = getFragmentManager().findFragmentByTag(PROGRESS_DIALOG_TAG); if (dialog != null && ((ProgressDialogFragment) dialog).getDialog() != null) { ((ProgressDialogFragment) dialog).getDialog().setTitle(mAlertTitle); ((ProgressDialogFragment) dialog).setMessage(mAlertMsg); } else if (progressDialog != null && progressDialog.getDialog() != null) { progressDialog.getDialog().setTitle(mAlertTitle); progressDialog.setMessage(mAlertMsg); } else { if (progressDialog != null) { dismissProgressDialog(); } progressDialog = ProgressDialogFragment.newInstance(getId(), mAlertTitle, mAlertMsg); progressDialog.show(getFragmentManager(), PROGRESS_DIALOG_TAG); } } } private void showProgressDialog() { mDialogState = DialogState.Progress; mAlertTitle = getString(R.string.uploading_data); mAlertMsg = getString(R.string.please_wait); restoreProgressDialog(); } private void updateProgressDialogMessage(String message) { if (mDialogState == DialogState.Progress) { mAlertTitle = getString(R.string.uploading_data); mAlertMsg = message; restoreProgressDialog(); } else { mDialogState = DialogState.None; dismissProgressDialog(); } } private void dismissProgressDialog() { final Fragment dialog = getFragmentManager().findFragmentByTag(PROGRESS_DIALOG_TAG); if (dialog != null && dialog != progressDialog) { // the UI may not yet have resolved the showing of the dialog. // use a handler to add the dismiss to the end of the queue. handler.post(new Runnable() { @Override public void run() { ((ProgressDialogFragment) dialog).dismiss(); } }); } if (progressDialog != null) { final ProgressDialogFragment scopedReference = progressDialog; progressDialog = null; // the UI may not yet have resolved the showing of the dialog. // use a handler to add the dismiss to the end of the queue. handler.post(new Runnable() { @Override public void run() { try { scopedReference.dismiss(); } catch (Exception e) { // ignore... we tried! } } }); } } private void restoreAlertDialog() { dismissProgressDialog(); Fragment dialog = getFragmentManager().findFragmentByTag("alertDialog"); if (dialog != null && ((AlertDialogFragment) dialog).getDialog() != null) { mDialogState = DialogState.Alert; ((AlertDialogFragment) dialog).getDialog().setTitle(mAlertTitle); ((AlertDialogFragment) dialog).setMessage(mAlertMsg); } else { AlertDialogFragment f = AlertDialogFragment.newInstance(getId(), mAlertTitle, mAlertMsg); mDialogState = DialogState.Alert; f.show(getFragmentManager(), "alertDialog"); } } @Override public void okAlertDialog() { mDialogState = DialogState.None; } /** * Creates an alert dialog with the given tite and message. If shouldExit is * set to true, the activity will exit when the user clicks "ok". * * @param title * @param shouldExit */ private void createAlertDialog(String title, String message) { mAlertTitle = title; mAlertMsg = message; restoreAlertDialog(); } @Override public void progressUpdate(int progress, int total) { String msg = getString(R.string.sending_items, progress, total); updateProgressDialogMessage(msg); } @Override public void uploadingComplete(InstanceUploadOutcome outcome) { try { mDialogState = DialogState.None; dismissProgressDialog(); } catch (Exception e) { WebLogger.getLogger(((ODKActivity) getActivity()).getAppName()).printStackTrace(e); WebLogger.getLogger(((ODKActivity) getActivity()).getAppName()).i(t, "Attempting to close a dialog that was not previously opened"); } BackgroundTaskFragment f = (BackgroundTaskFragment) getFragmentManager().findFragmentByTag("background"); f.clearUploadInstancesTask(); if (outcome.mAuthRequestingServer == null && ((ODKActivity) getActivity()).getUploadTableId() != null) { StringBuilder message = new StringBuilder(); Set<String> keys = outcome.mResults.keySet(); for (String id : keys) { Cursor results = null; try { Uri uri = Uri.withAppendedPath(InstanceProviderAPI.CONTENT_URI, ((ODKActivity) getActivity()).getAppName() + "/" + ((ODKActivity) getActivity()).getUploadTableId() + "/" + StringEscapeUtils.escapeHtml4(id)); results = getActivity().getContentResolver().query(uri, null, null, null, null); if (results.getCount() == 1 && results.moveToFirst()) { String name = ODKDatabaseUtils.get().getIndexAsString(results, results.getColumnIndex(InstanceColumns.DISPLAY_NAME)); message.append(name + " - " + outcome.mResults.get(id) + "\n\n"); } } finally { if (results != null) { results.close(); } } } if (message.length() == 0) { message.append(getString(R.string.no_forms_uploaded)); } createAlertDialog(getString(R.string.uploading_data), message.toString().trim()); } else { // add our list of completed uploads to "completed" // and remove them from our toSend list. if (outcome.mResults != null) { Set<String> uploadedInstances = outcome.mResults.keySet(); Iterator<String> itr = uploadedInstances.iterator(); while (itr.hasNext()) { String removeMe = itr.next(); boolean removed = mSelected.remove(removeMe); if (removed) { WebLogger.getLogger(((ODKActivity) getActivity()).getAppName()).i(t, removeMe + " was already sent, removing from queue before restarting task"); } } } // Bundle b = new Bundle(); // b.putString(AUTH_URI, url.toString()); // showDialog(AUTH_DIALOG, b); mUrl = outcome.mAuthRequestingServer; showAuthDialog(); } } private void showUnsent() { mShowUnsent = true; getLoaderManager().restartLoader(INSTANCE_UPLOADER_LIST_LOADER, null, this); } private void showAll() { mShowUnsent = false; getLoaderManager().restartLoader(INSTANCE_UPLOADER_LIST_LOADER, null, this); } @Override public boolean onLongClick(View v) { /** * Create a dialog with options to save and exit, save, or quit without * saving */ String[] items = { getString(R.string.show_unsent_forms), getString(R.string.show_sent_and_unsent_forms) }; AlertDialog alertDialog = new AlertDialog.Builder(getActivity()).setIcon(android.R.drawable.ic_dialog_info) .setTitle(getString(R.string.change_view)) .setNeutralButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }).setItems(items, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { switch (which) { case 0: // show unsent showUnsent(); break; case 1: // show all showAll(); break; case 2:// do nothing break; } } }).create(); alertDialog.show(); return true; } CursorLoader loader = null; @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. Uri baseUri; if (((ODKActivity) getActivity()).getUploadTableId() != null) { baseUri = Uri.withAppendedPath(InstanceProviderAPI.CONTENT_URI, ((ODKActivity) getActivity()).getAppName() + "/" + ((ODKActivity) getActivity()).getUploadTableId()); } else { baseUri = Uri.withAppendedPath(InstanceProviderAPI.CONTENT_URI, ((ODKActivity) getActivity()).getAppName()); } String selection; String selectionArgs[]; String sortOrder; if (mShowUnsent) { // show all unsent or failed-sending records selection = InstanceColumns.XML_PUBLISH_STATUS + " IS NULL or " + InstanceColumns.XML_PUBLISH_STATUS + "=?"; selectionArgs = new String[] { InstanceColumns.STATUS_SUBMISSION_FAILED }; sortOrder = InstanceColumns.DISPLAY_NAME + " ASC"; } else { // show all completed instances (ones ready to be submitted) selection = null; selectionArgs = null; sortOrder = InstanceColumns.DISPLAY_NAME + " ASC"; } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. loader = new CursorLoader(getActivity(), baseUri, null, selection, selectionArgs, sortOrder); return loader; } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mInstances.swapCursor(cursor); } @Override public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. mInstances.swapCursor(null); } @Override public void cancelProgressDialog() { // Notify the task that we want it to stop. // The task will call back through its completion // callbacks once it has halted, thereby updating the UI. BackgroundTaskFragment f = (BackgroundTaskFragment) getFragmentManager().findFragmentByTag("background"); f.cancelUploadInstancesTask(); } }