Java tutorial
/* Radiobeacon - Openbmap wifi and cell logger Copyright (C) 2013 wish7 This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.openbmap.activities; import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.WifiLock; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v4.app.FragmentManager; import android.support.v7.app.ActionBarActivity; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.widget.TabHost; import android.widget.Toast; import org.openbmap.Preferences; import org.openbmap.R; import org.openbmap.RadioBeacon; import org.openbmap.db.DataHelper; import org.openbmap.db.models.Session; import org.openbmap.soapclient.ExportGpxTask.ExportGpxTaskListener; import org.openbmap.soapclient.UploadTask.UploadTaskListener; import org.openbmap.utils.AlertDialogUtils; import org.openbmap.utils.OnAlertClickInterface; import org.openbmap.utils.TabManager; import org.openbmap.utils.TempFileUtils; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Locale; /** * Parent screen for hosting main screen */ public class StartscreenActivity extends ActionBarActivity implements SessionListFragment.SessionFragementListener, OnAlertClickInterface, UploadTaskListener, ExportGpxTaskListener { /** * */ private static final String UPLOAD_TASK = "upload_task"; private static final String EXPORT_GPX_TASK = "export_gpx_task"; private static final String TAG = StartscreenActivity.class.getSimpleName(); private static final String WIFILOCK_NAME = "UploadLock"; // alert builder ids private static final int ID_REPAIR_WIFI = 2; private static final int ID_MISSING_CREDENTIALS = 3; private static final int ID_EXPORT_FAILED = 4; private static final int ID_DELETE_SESSION = 5; private static final int ID_DELETE_PROCESSED = 6; private static final int ID_DELETE_ALL = 7; /** * Tab host control */ private TabHost mTabHost; /** * Tab host helper class */ private TabManager mTabManager; /** * Data helper */ private DataHelper mDataHelper; /** * Wifi lock, used while exporting */ private WifiLock wifiLock; /** * Persistent fragment saving upload task across activity re-creation */ private UploadTaskFragment mUploadTaskFragment; /** * Persistent fragment saving export gpx task across activity re-creation */ private ExportGpxTaskFragment mExportGpxTaskFragment; private FragmentManager fm; /** * Dialog indicating upload progress */ private ProgressDialog mUploadProgress; /** * Dialog indicating export gpx progress */ private ProgressDialog mExportGpxProgress; /** * List of all pending exports */ private final ArrayList<Integer> pendingExports = new ArrayList<Integer>(); /** * Counts successfully exported sessions */ private int completedExports; /** * Counts failed exports */ private int failedExports; @Override public final void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); // setup data connections mDataHelper = new DataHelper(this); final Bundle b = getIntent().getExtras(); if (b != null) { if (b.getString("command") != null && b.getString("command").equals("upload_all")) { uploadAllCommand(); } } initUi(savedInstanceState); initPersistantFragments(); } /** * Creates a persistent fragment for keeping export status. * If fragment has been created before, no new fragment is created * i.e. the Fragment is non-null, then it is currently being * retained across a configuration change. */ private void initPersistantFragments() { fm = getSupportFragmentManager(); mUploadTaskFragment = (UploadTaskFragment) fm.findFragmentByTag(UPLOAD_TASK); mExportGpxTaskFragment = (ExportGpxTaskFragment) fm.findFragmentByTag(EXPORT_GPX_TASK); if (mUploadTaskFragment == null) { Log.d(TAG, "Task fragment not found. Creating.."); mUploadTaskFragment = new UploadTaskFragment(); fm.beginTransaction().add(mUploadTaskFragment, UPLOAD_TASK).commitAllowingStateLoss(); initUploadTaskDialog(true); } else { Log.d(TAG, "Showing existing upload task fragment"); initUploadTaskDialog(false); showUploadTaskDialog(); } if (mExportGpxTaskFragment == null) { Log.d(TAG, "Task fragment not found. Creating.."); mExportGpxTaskFragment = new ExportGpxTaskFragment(); fm.beginTransaction().add(mExportGpxTaskFragment, EXPORT_GPX_TASK).commitAllowingStateLoss(); initExportGpxTaskDialog(true); } else { Log.d(TAG, "Showing existings export gpx task fragment"); initExportGpxTaskDialog(false); showExportGpxTaskDialog(); } } @Override protected void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); outState.putString("tab", mTabHost.getCurrentTabTag()); } @Override public final void onResume() { super.onResume(); createWifiLock(); // force an fragment refresh reloadListFragment(); } @Override public final void onPause() { releaseWifiLock(); super.onPause(); } @Override public final void onDestroy() { releaseWifiLock(); super.onDestroy(); } /* * Creates a new session record and starts HostActivity ("tracking" mode) */ @Override public final void startCommand() { final Intent newSession = new Intent(this, HostActivity.class); newSession.putExtra("new_session", true); startActivity(newSession); } /* * Resumes existing session * @param session session to resume */ @Override public final void resumeCommand(final int session) { final Intent resumeSession = new Intent(this, HostActivity.class); resumeSession.putExtra("_id", session); startActivity(resumeSession); } /** * Uploads session * Once exported, {@link onExportCompleted} is called * @param session * session id */ public final void uploadCommand(final int session) { acquireWifiLock(); pendingExports.clear(); completedExports = 0; failedExports = 0; pendingExports.add(session); mUploadTaskFragment.add(session); mUploadTaskFragment.execute(); showUploadTaskDialog(); updateUI(); } /* * Exports all sessions, which haven't been uploaded yet */ @Override public void uploadAllCommand() { acquireWifiLock(); pendingExports.clear(); completedExports = 0; failedExports = 0; final ArrayList<Integer> sessions = mDataHelper.getSessionList(); for (final int id : sessions) { if (!hasBeenUploaded(id)) { Log.i(TAG, "Adding " + id + " to export task list"); mUploadTaskFragment.add(id); pendingExports.add(id); } } mUploadTaskFragment.execute(); showUploadTaskDialog(); } /* (non-Javadoc) * @see org.openbmap.activities.SessionListFragment.SessionFragementListener#exportGpxCommand(int) */ @Override public void exportGpxCommand(final int id) { Log.i(TAG, "Exporting gpx"); final String path = this.getExternalFilesDir(null).getAbsolutePath(); final SimpleDateFormat date = new SimpleDateFormat("yyyyMMddhhmmss", Locale.US); final String filename = date.format(new Date(System.currentTimeMillis())) + "(" + String.valueOf(id) + ")" + ".gpx"; showExportGpxTaskDialog(); mExportGpxTaskFragment.execute(id, path, filename); } /* * Stops all active session */ @Override public final void stopCommand(final int id) { mDataHelper.invalidateActiveSessions(); // Signalling host activity to stop services final Intent intent = new Intent(RadioBeacon.INTENT_STOP_TRACKING); sendBroadcast(intent); updateUI(); } /** * Deletes session. * @param id * session id */ public final void deleteCommand(final int id) { AlertDialogUtils.newInstance(ID_DELETE_SESSION, getResources().getString(R.string.delete), getResources().getString(R.string.do_you_want_to_delete_this_session), String.valueOf(id), false) .show(getSupportFragmentManager(), "delete"); } /** * Deletes all session from pending list */ public final void deleteBatchCommand(final ArrayList<Integer> ids) { for (final int id : ids) { deleteConfirmed(id); } } /** * User has confirmed delete * @param id */ public final void deleteConfirmed(final int id) { if (id == RadioBeacon.SESSION_NOT_TRACKING) { return; } Log.i(TAG, "Deleting session " + id); // Signalling service stop request final Intent intent = new Intent(RadioBeacon.INTENT_STOP_TRACKING); sendBroadcast(intent); mDataHelper.deleteSession(id); final boolean skipDelete = PreferenceManager.getDefaultSharedPreferences(this) .getBoolean(Preferences.KEY_SKIP_DELETE, Preferences.VAL_SKIP_DELETE); if (!skipDelete) { // delete all temp files (.xml) TempFileUtils.cleanTempFiles(this); } // force UI list update updateUI(); Toast.makeText(getBaseContext(), R.string.deleted, Toast.LENGTH_SHORT).show(); } /* (non-Javadoc) * @see org.openbmap.activities.SessionListFragment.SessionFragementListener#deleteAllSessions(long) */ @Override public final void deleteAllCommand() { AlertDialogUtils .newInstance(ID_DELETE_ALL, getResources().getString(R.string.dialog_delete_all_sessions_title), getResources().getString(R.string.dialog_delete_all_sessions_message), null, false) .show(getSupportFragmentManager(), "delete_all"); } public final void deleteAllConfirmed() { // Signalling service stop request final Intent intent = new Intent(RadioBeacon.INTENT_STOP_TRACKING); sendBroadcast(intent); mDataHelper.deleteAllSession(); updateUI(); } /** * Updates session list fragment and informs * Sends broadcast to update UI. */ private void updateUI() { reloadListFragment(); // Force update on list fragements' adapters. // TODO check if we really need this final Intent intent1 = new Intent(RadioBeacon.INTENT_WIFI_UPDATE); sendBroadcast(intent1); final Intent intent2 = new Intent(RadioBeacon.INTENT_CELL_UPDATE); sendBroadcast(intent2); } /** * Create options menu. * @param menu Menu to inflate * @return always true */ @Override public final boolean onCreateOptionsMenu(final Menu menu) { final MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main_menu, menu); return true; } @Override public final boolean onOptionsItemSelected(final MenuItem item) { //Log.d(TAG, "OptionItemSelected, handled by StartscreenActivity"); switch (item.getItemId()) { case R.id.menu_create_new_session: // starts a new session startCommand(); break; case R.id.menu_settings: // Start settings activity startActivity(new Intent(this, SettingsActivity.class)); break; case R.id.menu_credits: // Start settings activity startActivity(new Intent(this, CreditsActivity.class)); break; default: break; } return super.onOptionsItemSelected(item); } /* (non-Javadoc) * @see org.openbmap.activities.SessionListFragment.SessionFragementListener#reload() */ @Override public final void reloadListFragment() { Log.i(TAG, "Refreshing session list fragment"); final SessionListFragment sessionFrag = (SessionListFragment) getSupportFragmentManager() .findFragmentByTag("session"); if (sessionFrag != null) { // TODO check if this is really necessary. Adapter should be able to handle updates automatically sessionFrag.refreshAdapter(); } } /** * Checks if session has been exported * @param id Session id * @return true if session has been uploaded or doesn't exist */ private boolean hasBeenUploaded(final int Id) { final DataHelper dataHelper = new DataHelper(this); final Session session = dataHelper.loadSession(Id); if (session != null) { return session.hasBeenExported(); } else { return true; } } /** * Toggles wifi enabled (to trigger reconnect) */ private void repairWifiConnection() { Log.i(TAG, "Reparing wifi connection"); final WifiManager wifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE); wifiManager.setWifiEnabled(false); wifiManager.setWifiEnabled(true); } /** * Inits Ui controls * @param savedInstanceState */ private void initUi(final Bundle savedInstanceState) { setContentView(R.layout.startscreen); getSupportActionBar().setTitle(R.string.title); getSupportActionBar().setSubtitle(R.string.subtitle); mTabHost = (TabHost) findViewById(android.R.id.tabhost); mTabHost.setup(); mTabManager = new TabManager(this, mTabHost, R.id.realtabcontent); mTabManager.addTab( mTabHost.newTabSpec("sessions").setIndicator(getResources().getString(R.string.sessions)), SessionListFragment.class, null); //mTabManager.addTab(mTabHost.newTabSpec("overview").setIndicator("Overview"), // OverviewFragment.class, null); if (savedInstanceState != null) { mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab")); } //SessionListFragment detailsFragment = (SessionListFragment) getSupportFragmentManager().findFragmentById(R.id.sessionListFragment); //detailsFragment.setOnSessionSelectedListener(this); } /* (non-Javadoc) * @see org.openbmap.utils.OnAlertClickInterface#onAlertPositiveClick(int) */ @Override public void onAlertPositiveClick(final int alertId, final String args) { if (alertId == ID_DELETE_ALL) { final int id = (args != null ? Integer.valueOf(args) : RadioBeacon.SESSION_NOT_TRACKING); stopCommand(id); deleteAllConfirmed(); } else if (alertId == ID_DELETE_SESSION) { final int id = (args != null ? Integer.valueOf(args) : RadioBeacon.SESSION_NOT_TRACKING); stopCommand(id); deleteConfirmed(id); } else if (alertId == ID_DELETE_PROCESSED) { final String candidates = (args != null ? String.valueOf(args) : ""); final ArrayList<Integer> list = new ArrayList<Integer>(); for (final String s : candidates.split("\\s*;\\s*")) { list.add(Integer.valueOf(s)); } deleteBatchCommand(list); } } /* (non-Javadoc) * @see org.openbmap.utils.OnAlertClickInterface#onAlertNegativeClick(int) */ @Override public void onAlertNegativeClick(final int alertId, final String args) { if (alertId == ID_DELETE_ALL) { return; } else if (alertId == ID_DELETE_SESSION) { return; } } /* (non-Javadoc) * @see org.openbmap.utils.OnAlertClickInterface#onAlertNeutralClick(int, java.lang.String) */ @Override public void onAlertNeutralClick(final int alertId, final String args) { // TODO Auto-generated method stub } /* (non-Javadoc) * @see org.openbmap.soapclient.ExportSessionTask.ExportTaskListener#onProgressUpdate(java.lang.Object[]) */ @Override public void onUploadProgressUpdate(final Object... values) { if (mUploadProgress != null) { mUploadProgress.setTitle((CharSequence) values[0]); mUploadProgress.setMessage((CharSequence) values[1]); mUploadProgress.setProgress((Integer) values[2]); } mUploadTaskFragment.retainProgress((String) values[0], (String) values[1], (Integer) values[2]); } /* (non-Javadoc) * @see org.openbmap.soapclient.ExportSessionTask.ExportTaskListener#onExportCompleted(int) */ @Override public void onUploadCompleted(final int id) { // mark as exported final Session session = mDataHelper.loadSession(id); session.hasBeenExported(true); session.isActive(false); mDataHelper.storeSession(session, false); completedExports += 1; Log.i(TAG, "Session " + id + " exported"); Log.i(TAG, "Exported " + completedExports + "/" + pendingExports.size() + " sessions"); if (pendingExports.size() == 1) { Log.i(TAG, "Export finished"); pendingExports.clear(); completedExports = 0; hideUploadTaskDialog(); releaseWifiLock(); deleteCommand(id); } else if (pendingExports.size() == completedExports + failedExports) { Log.i(TAG, "All exports finished"); if (failedExports > 0) { // at least one export failed AlertDialogUtils .newInstance(ID_EXPORT_FAILED, getResources().getString(R.string.export_error_title), getResources().getString(R.string.export_error), String.valueOf(id), true) .show(getSupportFragmentManager(), "failed"); } else { // if everything is ok, offer to delete String candidates = ""; for (final int one : pendingExports) { candidates += one + ";"; } AlertDialogUtils.newInstance(ID_DELETE_PROCESSED, getResources().getString(R.string.delete), getResources().getString(R.string.do_you_want_to_delete_processed_sessions), candidates, false).show(getSupportFragmentManager(), "failed"); } pendingExports.clear(); completedExports = 0; failedExports = 0; hideUploadTaskDialog(); releaseWifiLock(); // TODO move to onAlertNegative with ID_DELETE_PROCESSED reloadListFragment(); } else { // we've got more exports showUploadTaskDialog(); } } @Override public void onDryRunCompleted(final int id) { completedExports += 1; Log.i(TAG, "Session " + id + " exported"); Log.i(TAG, "Exported " + completedExports + "/" + pendingExports.size() + " sessions"); if (pendingExports.size() == 1) { Log.i(TAG, "Export simulated"); pendingExports.clear(); completedExports = 0; hideUploadTaskDialog(); releaseWifiLock(); } else if (pendingExports.size() == completedExports + failedExports) { Log.i(TAG, "All exports simulated"); if (failedExports > 0) { // at least one export failed AlertDialogUtils .newInstance(ID_EXPORT_FAILED, getResources().getString(R.string.export_error_title), getResources().getString(R.string.export_error), String.valueOf(id), true) .show(getSupportFragmentManager(), "failed"); } pendingExports.clear(); completedExports = 0; failedExports = 0; hideUploadTaskDialog(); releaseWifiLock(); reloadListFragment(); } else { // we've got more exports showUploadTaskDialog(); } } /* (non-Javadoc) * @see org.openbmap.soapclient.ExportSessionTask.ExportTaskListener#onExportFailed(java.lang.String) */ @Override public void onUploadFailed(final int id, final String error) { Log.e(TAG, "Export session " + id + " failed"); failedExports += 1; releaseWifiLock(); hideUploadTaskDialog(); final String errorText = (error == null || error.length() == 0) ? getResources().getString(R.string.export_error) : error; AlertDialogUtils.newInstance(ID_EXPORT_FAILED, getResources().getString(R.string.export_error_title), errorText, String.valueOf(id), true).show(getSupportFragmentManager(), "failed"); } /** * Creates or restores a progress dialogs * This dialog is not shown until calling showExportDialog() explicitly * @param newDialog * */ private void initUploadTaskDialog(final boolean newDialog) { if (newDialog) { mUploadProgress = new ProgressDialog(this); mUploadProgress.setCancelable(false); mUploadProgress.setIndeterminate(true); final String defaultTitle = getResources().getString(R.string.preparing_export); final String defaultMessage = getResources().getString(R.string.please_stay_patient); mUploadProgress.setTitle(defaultTitle); mUploadProgress.setMessage(defaultMessage); mUploadTaskFragment.retainProgress(defaultTitle, defaultMessage, (int) mUploadProgress.getProgress()); } else { mUploadProgress = new ProgressDialog(this); mUploadProgress.setCancelable(false); mUploadProgress.setIndeterminate(true); mUploadTaskFragment.restoreProgress(mUploadProgress); } } /** * Creates or restores a progress dialogs * This dialog is not shown until calling showExportDialog() explicitly * @param newDialog * */ private void initExportGpxTaskDialog(final boolean newDialog) { if (newDialog) { mExportGpxProgress = new ProgressDialog(this); mExportGpxProgress.setCancelable(false); mExportGpxProgress.setIndeterminate(true); final String defaultTitle = getResources().getString(R.string.exporting_gpx); final String defaultMessage = getResources().getString(R.string.please_stay_patient); mExportGpxProgress.setTitle(defaultTitle); mExportGpxProgress.setMessage(defaultMessage); mUploadTaskFragment.retainProgress(defaultTitle, defaultMessage, (int) mExportGpxProgress.getProgress()); } else { mExportGpxProgress = new ProgressDialog(this); mExportGpxProgress.setCancelable(false); mExportGpxProgress.setIndeterminate(true); mExportGpxTaskFragment.restoreProgress(mExportGpxProgress); } } /** * Opens upload dialog, if any */ private void showUploadTaskDialog() { if (mUploadProgress == null) { throw new IllegalArgumentException("Export progress dialog must not be null"); } if (mUploadTaskFragment.isExecuting()) { mUploadTaskFragment.restoreProgress(mUploadProgress); if (!mUploadProgress.isShowing()) { mUploadProgress.show(); } } } /** * Opens export gpx dialog, if any */ private void showExportGpxTaskDialog() { if (mExportGpxProgress == null) { throw new IllegalArgumentException("Export progress dialog must not be null"); } if (mExportGpxTaskFragment.isExecuting()) { mExportGpxTaskFragment.restoreProgress(mExportGpxProgress); if (!mExportGpxProgress.isShowing()) { mExportGpxProgress.show(); } } } /** * Closes export dialog */ private void hideUploadTaskDialog() { if (mUploadProgress != null) { mUploadProgress.cancel(); } } /** * Creates a new wifi lock, which isn't yet acquired */ private void createWifiLock() { // create wifi lock (will be acquired for version check/upload) final WifiManager wifiManager = (WifiManager) this.getSystemService(Context.WIFI_SERVICE); if (wifiManager != null) { wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, WIFILOCK_NAME); } else { Log.e(TAG, "Error acquiring wifi lock"); } } /** * Acquires wifi lock */ private void acquireWifiLock() { if (wifiLock == null) { Log.w(TAG, "Wifilock not found. Skipping acquisition.."); return; } if (!wifiLock.isHeld()) { wifiLock.acquire(); } else { Log.i(TAG, "Wifilock is hold already. Skipping acquisition.."); } } /** * Releases previously acquired wifi lock */ private void releaseWifiLock() { if (wifiLock != null && wifiLock.isHeld()) { wifiLock.release(); } } /* (non-Javadoc) * @see org.openbmap.soapclient.ExportGpxTask.ExportGpxTaskListener#onExportGpxCompleted(int) */ @Override public void onExportGpxCompleted(final int id) { Log.i(TAG, "GPX export completed"); } /* (non-Javadoc) * @see org.openbmap.soapclient.ExportGpxTask.ExportGpxTaskListener#onExportGpxFailed(int, java.lang.String) */ @Override public void onExportGpxFailed(final int id, final String error) { Log.e(TAG, "GPX export failed: " + error); Toast.makeText(this, R.string.gpx_export_failed, Toast.LENGTH_LONG).show(); } /* (non-Javadoc) * @see org.openbmap.soapclient.ExportGpxTask.ExportGpxTaskListener#onExportGpxProgressUpdate(java.lang.Object[]) */ @Override public void onExportGpxProgressUpdate(final Object[] values) { if (mExportGpxProgress != null) { mExportGpxProgress.setTitle((CharSequence) values[0]); mExportGpxProgress.setMessage((CharSequence) values[1]); mExportGpxProgress.setProgress((Integer) values[2]); } mExportGpxTaskFragment.retainProgress((String) values[0], (String) values[1], (Integer) values[2]); } }