/*
* Copyright (C) 2010-2017 Stichting Akvo (Akvo Foundation)
*
* This file is part of Akvo Flow.
*
* Akvo Flow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Akvo Flow 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Akvo Flow. If not, see <http://www.gnu.org/licenses/>.
*/
package org.akvo.flow.activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.database.SQLException;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.StatFs;
import android.text.Html;
import android.text.TextUtils;
import android.text.method.DigitsKeyListener;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;
import org.akvo.flow.BuildConfig;
import org.akvo.flow.R;
import org.akvo.flow.app.FlowApp;
import org.akvo.flow.async.ClearDataAsyncTask;
import org.akvo.flow.data.database.SurveyDbAdapter;
import org.akvo.flow.service.DataSyncService;
import org.akvo.flow.service.SurveyDownloadService;
import org.akvo.flow.service.UserRequestedApkUpdateService;
import org.akvo.flow.util.ConstantUtil;
import org.akvo.flow.util.PlatformUtil;
import org.akvo.flow.util.ViewUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import timber.log.Timber;
/**
* Displays the settings menu and handles the user choices
*
* @author Christopher Fagiani
*/
public class SettingsActivity extends BackActivity implements AdapterView.OnItemClickListener {
private static final String LABEL = "label";
private static final String DESC = "desc";
//TODO: this will be replaced by a year placed in a properties file
private static final String CURRENT_YEAR = "2017";
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
setupToolBar();
ArrayList<HashMap<String, String>> list = new ArrayList<>();
Resources resources = getResources();
list.add(createMap(resources.getString(R.string.prefoptlabel),
resources.getString(R.string.prefoptdesc)));
list.add(createMap(resources.getString(R.string.sendoptlabel),
resources.getString(R.string.sendoptdesc)));
list.add(createMap(resources.getString(R.string.reloadsurveyslabel),
resources.getString(R.string.reloadsurveysdesc)));
list.add(createMap(resources.getString(R.string.downloadsurveylabel),
resources.getString(R.string.downloadsurveydesc)));
list.add(createMap(resources.getString(R.string.poweroptlabel),
resources.getString(R.string.poweroptdesc)));
list.add(createMap(resources.getString(R.string.gpsstatuslabel),
resources.getString(R.string.gpsstatusdesc)));
list.add(createMap(resources.getString(R.string.reset_responses),
resources.getString(R.string.reset_responses_desc)));
list.add(createMap(resources.getString(R.string.resetall),
resources.getString(R.string.resetalldesc)));
list.add(createMap(resources.getString(R.string.checksd),
resources.getString(R.string.checksddesc)));
list.add(createMap(resources.getString(R.string.settings_app_update_title),
resources.getString(R.string.settings_app_update_description)));
list.add(createMap(resources.getString(R.string.aboutlabel),
resources.getString(R.string.aboutdesc)));
String[] fromKeys = {
LABEL, DESC
};
int[] toIds = {
R.id.optionLabel, R.id.optionDesc
};
ListView lv = (ListView) findViewById(android.R.id.list);
lv.setAdapter(new SettingsAdapter(this, list, R.layout.settingsdetail, fromKeys, toIds));
lv.setOnItemClickListener(this);
}
/**
* creates data structure for use in list adapter
*/
private HashMap<String, String> createMap(String label, String desc) {
HashMap<String, String> map = new HashMap<>();
map.put(LABEL, label);
map.put(DESC, desc);
return map;
}
/**
* when an item is clicked, use the label value to determine what option it
* was and then handle that type of action
*/
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TextView label = (TextView) view.findViewById(R.id.optionLabel);
if (label != null) {
String val = label.getText().toString();
Resources resources = getResources();
if (resources.getString(R.string.prefoptlabel).equals(val)) {
onPreferencesOptionTap();
} else if (resources.getString(R.string.poweroptlabel).equals(val)) {
onPowerManagementOptionTap();
} else if (resources.getString(R.string.gpsstatuslabel).equals(val)) {
onGpsStatusOptionTap();
} else if (resources.getString(R.string.aboutlabel).equals(val)) {
onAboutOptionTap(resources);
} else if (resources.getString(R.string.reloadsurveyslabel).equals(val)) {
onReloadAllSurveysOptionTap();
} else if (resources.getString(R.string.downloadsurveylabel).equals(val)) {
onDownloadFormOptionTap();
} else if (resources.getString(R.string.reset_responses).equals(val)) {
onDeleteCollectedDataOptionTap();
} else if (resources.getString(R.string.resetall).equals(val)) {
onDeleteEverythingOptionTap();
} else if (resources.getString(R.string.checksd).equals(val)) {
onCheckSdCardStateOptionTap(resources);
} else if (resources.getString(R.string.sendoptlabel).equals(val)) {
onSyncDataOptionTap(view);
} else if (resources.getString(R.string.settings_app_update_title).equals(val)) {
onUpdateAppOptionTap();
}
}
}
private void onUpdateAppOptionTap() {
startService(new Intent(this, UserRequestedApkUpdateService.class));
}
private void onSyncDataOptionTap(View view) {
Intent i = new Intent(view.getContext(), DataSyncService.class);
getApplicationContext().startService(i);
// terminate this activity
finish();
}
private void onCheckSdCardStateOptionTap(Resources resources) {
String state = Environment.getExternalStorageState();
StringBuilder builder = new StringBuilder();
if (state == null || !Environment.MEDIA_MOUNTED.equals(state)) {
builder.append("<b>").append(resources.getString(R.string.sdmissing))
.append("</b><br>");
} else {
builder.append(resources.getString(R.string.sdmounted)).append("<br>");
File f = Environment.getExternalStorageDirectory();
if (f != null) {
// normally, we could just do f.getFreeSpace() but that
// would tie us to later versions of Android. So for
// maximum compatibility, just use StatFS
StatFs fs = new StatFs(f.getAbsolutePath());
// We first cast the blocks and size values to float, to avoid an
// integer overflow scenario. Ideally we should use getFreeBlocksLong()
// instead, but it's only available in API level 18+
long fb = fs.getFreeBlocks();
long bs = fs.getBlockSize();
long space = fb * bs;
builder.append(resources.getString(R.string.sdcardspace))
.append(String.format(" %.2f", (double) space / (double) (1024 * 1024)));
}
}
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.checksd);
String text = Html.fromHtml(builder.toString()).toString();
dialog.setMessage(text);
dialog.setPositiveButton(R.string.okbutton, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
dialog.show();
}
private void onDeleteEverythingOptionTap() {
ViewUtil.showAdminAuthDialog(this, new ViewUtil.AdminAuthDialogListener() {
@Override
public void onAuthenticated() {
deleteData(false);
}
});
}
private void onDeleteCollectedDataOptionTap() {
ViewUtil.showAdminAuthDialog(this, new ViewUtil.AdminAuthDialogListener() {
@Override
public void onAuthenticated() {
deleteData(true);
}
});
}
private void onDownloadFormOptionTap() {
ViewUtil.showAdminAuthDialog(this, new ViewUtil.AdminAuthDialogListener() {
@Override
public void onAuthenticated() {
AlertDialog.Builder inputDialog = new AlertDialog.Builder(SettingsActivity.this);
inputDialog.setTitle(R.string.downloadsurveylabel);
inputDialog.setMessage(R.string.downloadsurveyinstr);
// Set an EditText view to get user input
final EditText input = new EditText(SettingsActivity.this);
input.setKeyListener(new DigitsKeyListener(false, false));
inputDialog.setView(input);
inputDialog.setPositiveButton(R.string.okbutton, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String surveyId = input.getText().toString().trim();
if (!TextUtils.isEmpty(surveyId)) {
Intent i = new Intent(SettingsActivity.this,
SurveyDownloadService.class);
i.putExtra(SurveyDownloadService.EXTRA_SURVEY_ID, surveyId);
SettingsActivity.this.startService(i);
}
}
});
inputDialog.setNegativeButton(R.string.cancelbutton, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// Canceled.
}
});
inputDialog.show();
}
});
}
private void onReloadAllSurveysOptionTap() {
ViewUtil.showAdminAuthDialog(this, new ViewUtil.AdminAuthDialogListener() {
@Override
public void onAuthenticated() {
AlertDialog.Builder builder = new AlertDialog.Builder(SettingsActivity.this);
builder.setTitle(R.string.conftitle);
builder.setMessage(R.string.reloadconftext);
builder.setPositiveButton(R.string.okbutton, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Context c = SettingsActivity.this;
Intent i = new Intent(c, SurveyDownloadService.class);
i.putExtra(SurveyDownloadService.EXTRA_DELETE_SURVEYS, true);
c.startService(i);
}
});
builder.setNegativeButton(R.string.cancelbutton,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
builder.show();
}
});
}
private void onAboutOptionTap(Resources resources) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
String txt = resources
.getString(R.string.about_text, CURRENT_YEAR, BuildConfig.VERSION_NAME);
builder.setTitle(R.string.abouttitle);
builder.setMessage(txt);
builder.setPositiveButton(R.string.okbutton, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
builder.show();
}
private void onGpsStatusOptionTap() {
try {
Intent i = new Intent(ConstantUtil.GPS_STATUS_INTENT);
startActivity(i);
} catch (Exception e) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.nogpsstatus);
builder.setPositiveButton(R.string.okbutton, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
builder.show();
}
}
private void onPowerManagementOptionTap() {
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
if (!wm.isWifiEnabled()) {
wm.setWifiEnabled(true);
} else {
wm.setWifiEnabled(false);
}
}
private void onPreferencesOptionTap() {
Intent i = new Intent(this, PreferencesActivity.class);
startActivity(i);
}
private boolean unsentData() throws SQLException {
SurveyDbAdapter db = new SurveyDbAdapter(this);
try {
db.open();
return db.getUnsyncedTransmissions().size() > 0;
} finally {
if (db != null) {
db.close();
}
}
}
/**
* Permanently deletes data from the device. If unsubmitted data is found on
* the database, the user will be prompted with a message to confirm the
* operation.
*
* @param responsesOnly Flag to specify a partial deletion (user generated
* data).
*/
private void deleteData(final boolean responsesOnly) throws SQLException {
try {
int messageId;
if (unsentData()) {
messageId = R.string.unsentdatawarning;
} else if (responsesOnly) {
messageId = R.string.delete_responses_warning;
} else {
messageId = R.string.deletealldatawarning;
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(messageId)
.setCancelable(true)
.setPositiveButton(R.string.okbutton, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
if (!responsesOnly) {
// Delete everything implies logging the current user out (if any)
FlowApp.getApp().setUser(null);
}
new ClearDataAsyncTask(SettingsActivity.this).execute(responsesOnly);
}
})
.setNegativeButton(R.string.cancelbutton,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
builder.show();
} catch (SQLException e) {
Timber.e(e, e.getMessage());
Toast.makeText(this, R.string.clear_data_error, Toast.LENGTH_SHORT).show();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
}
return onOptionsItemSelected(item);
}
private static class SettingsAdapter extends SimpleAdapter {
public SettingsAdapter(Context context, List<? extends Map<String, ?>> data, int resource,
String[] from,
int[] to) {
super(context, data, resource, from, to);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
// Alternate background
int attr = position % 2 == 0 ? R.attr.listitem_bg1 : R.attr.listitem_bg2;
final int res = PlatformUtil.getResource(view.getContext(), attr);
view.setBackgroundResource(res);
return view;
}
}
}