Java tutorial
/* * 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.AlertDialog; import android.arch.lifecycle.LiveData; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnLongClickListener; import android.widget.AdapterView; import android.widget.Button; import android.widget.ListView; import com.google.android.gms.analytics.HitBuilders; import org.odk.collect.android.R; import org.odk.collect.android.adapters.InstanceUploaderAdapter; import org.odk.collect.android.application.Collect; import org.odk.collect.android.dao.InstancesDao; import org.odk.collect.android.listeners.DiskSyncListener; import org.odk.collect.android.listeners.PermissionListener; import org.odk.collect.android.preferences.GeneralSharedPreferences; import org.odk.collect.android.preferences.PreferencesActivity; import org.odk.collect.android.preferences.Transport; import org.odk.collect.android.tasks.InstanceSyncTask; import org.odk.collect.android.tasks.sms.SmsNotificationReceiver; import org.odk.collect.android.tasks.sms.SmsService; import org.odk.collect.android.tasks.sms.contracts.SmsSubmissionManagerContract; import org.odk.collect.android.tasks.sms.models.SmsSubmission; import org.odk.collect.android.upload.AutoSendWorker; import org.odk.collect.android.utilities.PlayServicesUtil; import org.odk.collect.android.utilities.ToastUtils; import java.util.List; import javax.inject.Inject; import androidx.work.State; import androidx.work.WorkManager; import androidx.work.WorkStatus; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import timber.log.Timber; import static org.odk.collect.android.preferences.PreferenceKeys.KEY_PROTOCOL; import static org.odk.collect.android.preferences.PreferenceKeys.KEY_SUBMISSION_TRANSPORT_TYPE; import static org.odk.collect.android.tasks.sms.SmsSender.SMS_INSTANCE_ID; import static org.odk.collect.android.utilities.PermissionUtils.finishAllActivities; import static org.odk.collect.android.utilities.PermissionUtils.requestReadPhoneStatePermission; import static org.odk.collect.android.utilities.PermissionUtils.requestSendSMSPermission; import static org.odk.collect.android.utilities.PermissionUtils.requestStoragePermissions; /** * Responsible for displaying all the valid forms in the forms directory. Stores * the path to selected form for use by {@link MainMenuActivity}. * * @author Carl Hartung (carlhartung@gmail.com) * @author Yaw Anokwa (yanokwa@gmail.com) */ public class InstanceUploaderList extends InstanceListActivity implements OnLongClickListener, DiskSyncListener, AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks<Cursor> { private static final String SHOW_ALL_MODE = "showAllMode"; private static final String INSTANCE_UPLOADER_LIST_SORTING_ORDER = "instanceUploaderListSortingOrder"; private static final int INSTANCE_UPLOADER = 0; @BindView(R.id.upload_button) Button uploadButton; @BindView(R.id.sms_upload_button) Button smsUploadButton; @BindView(R.id.toggle_button) Button toggleSelsButton; private InstancesDao instancesDao; private InstanceSyncTask instanceSyncTask; private boolean showAllMode; // Default to true so the send button is disabled until the worker status is updated by the // observer private boolean autoSendOngoing = true; private final BroadcastReceiver smsForegroundReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { //Stops the notification from being sent to others that are listening for this broadcast. abortBroadcast(); //deletes submission since the app is in the foreground and the NotificationReceiver won't be triggered. if (intent.hasExtra(SMS_INSTANCE_ID)) { deleteIfSubmissionCompleted(intent.getStringExtra(SMS_INSTANCE_ID)); } } }; @Inject SmsService smsService; @Inject SmsSubmissionManagerContract smsSubmissionManager; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Timber.i("onCreate"); // set title setTitle(getString(R.string.send_data)); setContentView(R.layout.instance_uploader_list); ButterKnife.bind(this); getComponent().inject(this); if (savedInstanceState != null) { showAllMode = savedInstanceState.getBoolean(SHOW_ALL_MODE); } requestStoragePermissions(this, new PermissionListener() { @Override public void granted() { init(); } @Override public void denied() { // The activity has to finish because ODK Collect cannot function without these permissions. finishAllActivities(InstanceUploaderList.this); } }); } /** * Determines how an upload should be handled by checking the transport being used. * If the transport is set to SMS or Internet then either is used respectively else it's * "Both" so the button IDs drive the decision. * * @param button that triggers an upload */ @OnClick({ R.id.upload_button, R.id.sms_upload_button }) public void onUploadButtonsClicked(Button button) { Transport transport = Transport .fromPreference(GeneralSharedPreferences.getInstance().get(KEY_SUBMISSION_TRANSPORT_TYPE)); if (!transport.equals(Transport.Sms) && button.getId() == R.id.upload_button) { ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService( Context.CONNECTIVITY_SERVICE); NetworkInfo ni = connectivityManager.getActiveNetworkInfo(); if (ni == null || !ni.isConnected()) { ToastUtils.showShortToast(R.string.no_connection); return; } if (autoSendOngoing) { ToastUtils.showShortToast(R.string.send_in_progress); return; } } int checkedItemCount = getCheckedCount(); if (checkedItemCount > 0) { // items selected uploadSelectedFiles(button.getId()); setAllToCheckedState(listView, false); toggleButtonLabel(findViewById(R.id.toggle_button), listView); uploadButton.setEnabled(false); smsUploadButton.setEnabled(false); } else { // no items selected ToastUtils.showLongToast(R.string.noselect_error); } } /** * Changes the default upload button text if "Both" transport is * enabled and sets SMS upload button visibility */ private void setupUploadButtons() { Transport transport = Transport .fromPreference(GeneralSharedPreferences.getInstance().get(KEY_SUBMISSION_TRANSPORT_TYPE)); if (transport.equals(Transport.Both)) { uploadButton.setText(R.string.send_selected_data_internet); smsUploadButton.setVisibility(View.VISIBLE); } else { smsUploadButton.setVisibility(View.GONE); uploadButton.setText(R.string.send_selected_data); } } private void init() { setupUploadButtons(); instancesDao = new InstancesDao(); toggleSelsButton.setLongClickable(true); toggleSelsButton.setOnClickListener(v -> { ListView lv = listView; boolean allChecked = toggleChecked(lv); toggleButtonLabel(toggleSelsButton, lv); uploadButton.setEnabled(allChecked); smsUploadButton.setEnabled(allChecked); if (allChecked) { for (int i = 0; i < lv.getCount(); i++) { selectedInstances.add(lv.getItemIdAtPosition(i)); } } else { selectedInstances.clear(); } }); toggleSelsButton.setOnLongClickListener(this); setupAdapter(); listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); listView.setItemsCanFocus(false); listView.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { uploadButton.setEnabled(areCheckedItems()); smsUploadButton.setEnabled(areCheckedItems()); }); instanceSyncTask = new InstanceSyncTask(); instanceSyncTask.setDiskSyncListener(this); instanceSyncTask.execute(); sortingOptions = new String[] { getString(R.string.sort_by_name_asc), getString(R.string.sort_by_name_desc), getString(R.string.sort_by_date_asc), getString(R.string.sort_by_date_desc) }; getSupportLoaderManager().initLoader(LOADER_ID, null, this); // Start observer that sets autoSendOngoing field based on AutoSendWorker status updateAutoSendStatus(); } /** * Updates whether an auto-send job is ongoing. */ private void updateAutoSendStatus() { LiveData<List<WorkStatus>> statuses = WorkManager.getInstance() .getStatusesForUniqueWorkLiveData(AutoSendWorker.class.getName()); statuses.observe(this, workStatuses -> { if (workStatuses != null) { for (WorkStatus status : workStatuses) { if (status.getState().equals(State.RUNNING)) { autoSendOngoing = true; return; } } autoSendOngoing = false; } }); } @Override protected void onResume() { if (instanceSyncTask != null) { instanceSyncTask.setDiskSyncListener(this); if (instanceSyncTask.getStatus() == AsyncTask.Status.FINISHED) { syncComplete(instanceSyncTask.getStatusMessage()); } } super.onResume(); IntentFilter filter = new IntentFilter(SmsNotificationReceiver.SMS_NOTIFICATION_ACTION); // The default priority is 0. Positive values will be before // the default, lower values will be after it. filter.setPriority(1); registerReceiver(smsForegroundReceiver, filter); setupUploadButtons(); } @Override protected void onPause() { if (instanceSyncTask != null) { instanceSyncTask.setDiskSyncListener(null); } super.onPause(); unregisterReceiver(smsForegroundReceiver); } @Override public void syncComplete(@NonNull String result) { Timber.i("Disk scan complete"); hideProgressBarAndAllow(); showSnackbar(result); } private void uploadSelectedFiles(int buttonId) { long[] instanceIds = listView.getCheckedItemIds(); Transport transport = Transport .fromPreference(GeneralSharedPreferences.getInstance().get(KEY_SUBMISSION_TRANSPORT_TYPE)); if (transport.equals(Transport.Sms) || buttonId == R.id.sms_upload_button) { requestSendSMSPermission(this, new PermissionListener() { @Override public void granted() { smsService.submitForms(instanceIds); } @Override public void denied() { } }); } else { String server = (String) GeneralSharedPreferences.getInstance().get(KEY_PROTOCOL); if (server.equalsIgnoreCase(getString(R.string.protocol_google_sheets))) { // if it's Sheets, start the Sheets uploader // first make sure we have a google account selected if (PlayServicesUtil.isGooglePlayServicesAvailable(this)) { Intent i = new Intent(this, GoogleSheetsUploaderActivity.class); i.putExtra(FormEntryActivity.KEY_INSTANCES, instanceIds); startActivityForResult(i, INSTANCE_UPLOADER); } else { PlayServicesUtil.showGooglePlayServicesAvailabilityErrorDialog(this); } } else { // otherwise, do the normal aggregate/other thing. Intent i = new Intent(this, InstanceUploaderActivity.class); i.putExtra(FormEntryActivity.KEY_INSTANCES, instanceIds); // Not required but without this permission a Device ID attached to a request will be empty. requestReadPhoneStatePermission(this, new PermissionListener() { @Override public void granted() { startActivityForResult(i, INSTANCE_UPLOADER); } @Override public void denied() { startActivityForResult(i, INSTANCE_UPLOADER); } }, false); } } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.instance_uploader_menu, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_preferences: createPreferencesMenu(); return true; case R.id.menu_change_view: showSentAndUnsentChoices(); return true; } return super.onOptionsItemSelected(item); } private void createPreferencesMenu() { Intent i = new Intent(this, PreferencesActivity.class); startActivity(i); } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long rowId) { if (listView.isItemChecked(position)) { selectedInstances.add(listView.getItemIdAtPosition(position)); } else { selectedInstances.remove(listView.getItemIdAtPosition(position)); } uploadButton.setEnabled(areCheckedItems()); smsUploadButton.setEnabled(areCheckedItems()); Button toggleSelectionsButton = findViewById(R.id.toggle_button); toggleButtonLabel(toggleSelectionsButton, listView); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(SHOW_ALL_MODE, showAllMode); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { if (resultCode == RESULT_CANCELED) { selectedInstances.clear(); return; } switch (requestCode) { // returns with a form path, start entry case INSTANCE_UPLOADER: if (intent.getBooleanExtra(FormEntryActivity.KEY_SUCCESS, false)) { listView.clearChoices(); if (listAdapter.isEmpty()) { finish(); } } break; } super.onActivityResult(requestCode, resultCode, intent); } private void setupAdapter() { listAdapter = new InstanceUploaderAdapter(this, null); listView.setAdapter(listAdapter); checkPreviouslyCheckedItems(); } @Override protected String getSortingOrderKey() { return INSTANCE_UPLOADER_LIST_SORTING_ORDER; } @Override protected void updateAdapter() { getSupportLoaderManager().restartLoader(LOADER_ID, null, this); } @NonNull @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { showProgressBar(); if (showAllMode) { return instancesDao.getCompletedUndeletedInstancesCursorLoader(getFilterText(), getSortingOrder()); } else { return instancesDao.getFinalizedInstancesCursorLoader(getFilterText(), getSortingOrder()); } } @Override public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) { hideProgressBarIfAllowed(); listAdapter.changeCursor(cursor); checkPreviouslyCheckedItems(); toggleButtonLabel(findViewById(R.id.toggle_button), listView); } @Override public void onLoaderReset(@NonNull Loader<Cursor> loader) { listAdapter.swapCursor(null); } @Override public boolean onLongClick(View v) { return showSentAndUnsentChoices(); } /* * Create a dialog with options to save and exit, save, or quit without * saving */ private boolean showSentAndUnsentChoices() { String[] items = { getString(R.string.show_unsent_forms), getString(R.string.show_sent_and_unsent_forms) }; AlertDialog alertDialog = new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_info) .setTitle(getString(R.string.change_view)) .setNeutralButton(getString(R.string.cancel), (dialog, id) -> { dialog.cancel(); }).setItems(items, (dialog, which) -> { switch (which) { case 0: // show unsent showAllMode = false; updateAdapter(); break; case 1: // show all showAllMode = true; updateAdapter(); Collect.getInstance().getDefaultTracker().send(new HitBuilders.EventBuilder() .setCategory("FilterSendForms").setAction("SentAndUnsent").build()); break; case 2:// do nothing break; } }).create(); alertDialog.show(); return true; } @Override protected void onDestroy() { super.onDestroy(); if (listAdapter != null) { ((InstanceUploaderAdapter) listAdapter).onDestroy(); } } private void deleteIfSubmissionCompleted(String instanceId) { SmsSubmission model = smsSubmissionManager.getSubmissionModel(instanceId); if (model.isSubmissionComplete()) { smsSubmissionManager.forgetSubmission(model.getInstanceId()); } } }