/*
* Copyright (C) 2011 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.preferences;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.provider.MediaStore.Images;
import android.text.InputFilter;
import android.text.Spanned;
import org.odk.collect.android.R;
import org.odk.collect.android.logic.FormController;
import org.odk.collect.android.logic.PropertyManager;
import java.util.ArrayList;
/**
* Handles general preferences.
*
* @author Thomas Smyth, Sassafras Tech Collective (tom@sassafrastech.com; constraint behavior option)
*/
public class PreferencesActivity extends PreferenceActivity implements
OnPreferenceChangeListener {
public static final String INTENT_KEY_ADMIN_MODE = "adminMode";
protected static final int IMAGE_CHOOSER = 0;
// PUT ALL PREFERENCE KEYS HERE
public static final String KEY_INFO = "info";
public static final String KEY_LAST_VERSION = "lastVersion";
public static final String KEY_FIRST_RUN = "firstRun";
public static final String KEY_SHOW_SPLASH = "showSplash";
public static final String KEY_SPLASH_PATH = "splashPath";
public static final String KEY_FONT_SIZE = "font_size";
public static final String KEY_DELETE_AFTER_SEND = "delete_send";
public static final String KEY_PROTOCOL = "protocol";
public static final String KEY_PROTOCOL_SETTINGS = "protocol_settings";
// leaving these in the main screen because username can be used as a
// pre-fill
// value in a form
public static final String KEY_SELECTED_GOOGLE_ACCOUNT = "selected_google_account";
public static final String KEY_USERNAME = "username";
public static final String KEY_PASSWORD = "password";
// AGGREGATE SPECIFIC
public static final String KEY_SERVER_URL = "server_url";
// GME SPECIFIC
public static final String KEY_GME_PROJECT_ID = "gme_project_id";
public static final String KEY_GME_ID_HASHMAP = "gme_id_hashmap";
// OTHER SPECIFIC
public static final String KEY_FORMLIST_URL = "formlist_url";
public static final String KEY_SUBMISSION_URL = "submission_url";
public static final String NAVIGATION_SWIPE = "swipe";
public static final String NAVIGATION_BUTTONS = "buttons";
public static final String NAVIGATION_SWIPE_BUTTONS = "swipe_buttons";
public static final String CONSTRAINT_BEHAVIOR_ON_SWIPE = "on_swipe";
public static final String CONSTRAINT_BEHAVIOR_ON_FINALIZE = "on_finalize";
public static final String CONSTRAINT_BEHAVIOR_DEFAULT = "on_swipe";
public static final String KEY_COMPLETED_DEFAULT = "default_completed";
public static final String KEY_HIGH_RESOLUTION = "high_resolution";
public static final String KEY_AUTH = "auth";
public static final String KEY_AUTOSEND_WIFI = "autosend_wifi";
public static final String KEY_AUTOSEND_NETWORK = "autosend_network";
public static final String KEY_NAVIGATION = "navigation";
public static final String KEY_CONSTRAINT_BEHAVIOR = "constraint_behavior";
private PreferenceScreen mSplashPathPreference;
private ListPreference mSelectedGoogleAccountPreference;
private ListPreference mFontSizePreference;
private ListPreference mNavigationPreference;
private ListPreference mConstraintBehaviorPreference;
private CheckBoxPreference mAutosendWifiPreference;
private CheckBoxPreference mAutosendNetworkPreference;
private ListPreference mProtocolPreference;
private PreferenceScreen mProtocolSettings;
protected EditTextPreference mUsernamePreference;
protected EditTextPreference mPasswordPreference;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
setTitle(getString(R.string.app_name) + " > "
+ getString(R.string.general_preferences));
// not super safe, but we're just putting in this mode to help
// administrate
// would require code to access it
final boolean adminMode = getIntent().getBooleanExtra(INTENT_KEY_ADMIN_MODE, false);
SharedPreferences adminPreferences = getSharedPreferences(
AdminPreferencesActivity.ADMIN_PREFERENCES, 0);
// assign all the preferences in advance because changing one often
// affects another
// also avoids npe
PreferenceCategory autosendCategory = (PreferenceCategory) findPreference(getString(R.string.autosend));
mAutosendWifiPreference = (CheckBoxPreference) findPreference(KEY_AUTOSEND_WIFI);
mAutosendNetworkPreference = (CheckBoxPreference) findPreference(KEY_AUTOSEND_NETWORK);
PreferenceCategory serverCategory = (PreferenceCategory) findPreference(getString(R.string.server_preferences));
mProtocolPreference = (ListPreference) findPreference(KEY_PROTOCOL);
mSelectedGoogleAccountPreference = (ListPreference) findPreference(KEY_SELECTED_GOOGLE_ACCOUNT);
PreferenceCategory clientCategory = (PreferenceCategory) findPreference(getString(R.string.client));
mNavigationPreference = (ListPreference) findPreference(KEY_NAVIGATION);
mFontSizePreference = (ListPreference) findPreference(KEY_FONT_SIZE);
Preference defaultFinalized = findPreference(KEY_COMPLETED_DEFAULT);
Preference deleteAfterSend = findPreference(KEY_DELETE_AFTER_SEND);
mSplashPathPreference = (PreferenceScreen) findPreference(KEY_SPLASH_PATH);
mConstraintBehaviorPreference = (ListPreference) findPreference(KEY_CONSTRAINT_BEHAVIOR);
mUsernamePreference = (EditTextPreference) findPreference(PreferencesActivity.KEY_USERNAME);
mPasswordPreference = (EditTextPreference) findPreference(PreferencesActivity.KEY_PASSWORD);
mProtocolSettings = (PreferenceScreen) findPreference(KEY_PROTOCOL_SETTINGS);
boolean autosendWifiAvailable = adminPreferences.getBoolean(
AdminPreferencesActivity.KEY_AUTOSEND_WIFI, true);
if (!(autosendWifiAvailable || adminMode)) {
autosendCategory.removePreference(mAutosendWifiPreference);
}
boolean autosendNetworkAvailable = adminPreferences.getBoolean(
AdminPreferencesActivity.KEY_AUTOSEND_NETWORK, true);
if (!(autosendNetworkAvailable || adminMode)) {
autosendCategory.removePreference(mAutosendNetworkPreference);
}
if (!(autosendNetworkAvailable || autosendWifiAvailable || adminMode)) {
getPreferenceScreen().removePreference(autosendCategory);
}
mProtocolPreference = (ListPreference) findPreference(KEY_PROTOCOL);
mProtocolPreference.setSummary(mProtocolPreference.getEntry());
Intent prefIntent = null;
if (mProtocolPreference.getValue().equals(getString(R.string.protocol_odk_default))) {
setDefaultAggregatePaths();
prefIntent = new Intent(this, AggregatePreferencesActivity.class);
} else {
// other
prefIntent = new Intent(this, OtherPreferencesActivity.class);
}
prefIntent.putExtra(INTENT_KEY_ADMIN_MODE, adminMode);
mProtocolSettings.setIntent(prefIntent);
mProtocolPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference,
Object newValue) {
String oldValue = ((ListPreference) preference).getValue();
int index = ((ListPreference) preference)
.findIndexOfValue(newValue.toString());
String entry = (String) ((ListPreference) preference)
.getEntries()[index];
String value = (String) ((ListPreference) preference)
.getEntryValues()[index];
((ListPreference) preference).setSummary(entry);
Intent prefIntent = null;
if (value.equals(getString(R.string.protocol_odk_default))) {
setDefaultAggregatePaths();
prefIntent = new Intent(PreferencesActivity.this, AggregatePreferencesActivity.class);
} else {
// other
prefIntent = new Intent(PreferencesActivity.this, OtherPreferencesActivity.class);
}
prefIntent.putExtra(INTENT_KEY_ADMIN_MODE, adminMode);
mProtocolSettings.setIntent(prefIntent);
if (!((String) newValue).equals(oldValue)) {
startActivity(prefIntent);
}
return true;
}
});
boolean changeProtocol = adminPreferences.getBoolean(
AdminPreferencesActivity.KEY_CHANGE_SERVER, true);
if (!(changeProtocol || adminMode)) {
serverCategory.removePreference(mProtocolPreference);
}
boolean changeProtocolSettings = adminPreferences.getBoolean(
AdminPreferencesActivity.KEY_CHANGE_PROTOCOL_SETTINGS, true);
if (!(changeProtocolSettings || adminMode)) {
serverCategory.removePreference(mProtocolSettings);
}
// get list of google accounts
final Account[] accounts = AccountManager.get(getApplicationContext())
.getAccountsByType("com.google");
ArrayList<String> accountEntries = new ArrayList<String>();
ArrayList<String> accountValues = new ArrayList<String>();
for (int i = 0; i < accounts.length; i++) {
accountEntries.add(accounts[i].name);
accountValues.add(accounts[i].name);
}
accountEntries.add(getString(R.string.no_account));
accountValues.add("");
mSelectedGoogleAccountPreference.setEntries(accountEntries
.toArray(new String[accountEntries.size()]));
mSelectedGoogleAccountPreference.setEntryValues(accountValues
.toArray(new String[accountValues.size()]));
mSelectedGoogleAccountPreference
.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference,
Object newValue) {
int index = ((ListPreference) preference)
.findIndexOfValue(newValue.toString());
String value = (String) ((ListPreference) preference)
.getEntryValues()[index];
((ListPreference) preference).setSummary(value);
return true;
}
});
mSelectedGoogleAccountPreference
.setSummary(mSelectedGoogleAccountPreference.getValue());
boolean googleAccountAvailable = adminPreferences.getBoolean(
AdminPreferencesActivity.KEY_CHANGE_GOOGLE_ACCOUNT, true);
if (!(googleAccountAvailable || adminMode)) {
serverCategory.removePreference(mSelectedGoogleAccountPreference);
}
mUsernamePreference.setOnPreferenceChangeListener(this);
mUsernamePreference.setSummary(mUsernamePreference.getText());
mUsernamePreference.getEditText().setFilters(
new InputFilter[] { getReturnFilter() });
boolean usernameAvailable = adminPreferences.getBoolean(
AdminPreferencesActivity.KEY_CHANGE_USERNAME, true);
if (!(usernameAvailable || adminMode)) {
serverCategory.removePreference(mUsernamePreference);
}
mPasswordPreference
.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference,
Object newValue) {
String pw = newValue.toString();
if (pw.length() > 0) {
mPasswordPreference.setSummary("********");
} else {
mPasswordPreference.setSummary("");
}
return true;
}
});
if (mPasswordPreference.getText() != null
&& mPasswordPreference.getText().length() > 0) {
mPasswordPreference.setSummary("********");
}
mPasswordPreference.getEditText().setFilters(
new InputFilter[] { getReturnFilter() });
boolean passwordAvailable = adminPreferences.getBoolean(
AdminPreferencesActivity.KEY_CHANGE_PASSWORD, true);
if (!(passwordAvailable || adminMode)) {
serverCategory.removePreference(mPasswordPreference);
}
boolean navigationAvailable = adminPreferences.getBoolean(
AdminPreferencesActivity.KEY_NAVIGATION, true);
mNavigationPreference.setSummary(mNavigationPreference.getEntry());
mNavigationPreference
.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference,
Object newValue) {
int index = ((ListPreference) preference)
.findIndexOfValue(newValue.toString());
String entry = (String) ((ListPreference) preference)
.getEntries()[index];
((ListPreference) preference).setSummary(entry);
return true;
}
});
if (!(navigationAvailable || adminMode)) {
clientCategory.removePreference(mNavigationPreference);
}
boolean constraintBehaviorAvailable = adminPreferences.getBoolean(
AdminPreferencesActivity.KEY_CONSTRAINT_BEHAVIOR, true);
mConstraintBehaviorPreference.setSummary(mConstraintBehaviorPreference.getEntry());
mConstraintBehaviorPreference
.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference,
Object newValue) {
int index = ((ListPreference) preference)
.findIndexOfValue(newValue.toString());
String entry = (String) ((ListPreference) preference)
.getEntries()[index];
((ListPreference) preference).setSummary(entry);
return true;
}
});
if (!(constraintBehaviorAvailable || adminMode)) {
clientCategory.removePreference(mConstraintBehaviorPreference);
}
boolean fontAvailable = adminPreferences.getBoolean(
AdminPreferencesActivity.KEY_CHANGE_FONT_SIZE, true);
mFontSizePreference.setSummary(mFontSizePreference.getEntry());
mFontSizePreference
.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference,
Object newValue) {
int index = ((ListPreference) preference)
.findIndexOfValue(newValue.toString());
String entry = (String) ((ListPreference) preference)
.getEntries()[index];
((ListPreference) preference).setSummary(entry);
return true;
}
});
if (!(fontAvailable || adminMode)) {
clientCategory.removePreference(mFontSizePreference);
}
boolean defaultAvailable = adminPreferences.getBoolean(
AdminPreferencesActivity.KEY_DEFAULT_TO_FINALIZED, true);
if (!(defaultAvailable || adminMode)) {
clientCategory.removePreference(defaultFinalized);
}
boolean deleteAfterAvailable = adminPreferences.getBoolean(
AdminPreferencesActivity.KEY_DELETE_AFTER_SEND, true);
if(!(deleteAfterAvailable || adminMode)) {
clientCategory.removePreference(deleteAfterSend);
}
boolean resolutionAvailable = adminPreferences.getBoolean(
AdminPreferencesActivity.KEY_HIGH_RESOLUTION, true);
Preference highResolution = findPreference(KEY_HIGH_RESOLUTION);
if (!(resolutionAvailable || adminMode)) {
clientCategory.removePreference(highResolution);
}
mSplashPathPreference
.setOnPreferenceClickListener(new OnPreferenceClickListener() {
private void launchImageChooser() {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.setType("image/*");
startActivityForResult(i,
PreferencesActivity.IMAGE_CHOOSER);
}
@Override
public boolean onPreferenceClick(Preference preference) {
// if you have a value, you can clear it or select new.
CharSequence cs = mSplashPathPreference.getSummary();
if (cs != null && cs.toString().contains("/")) {
final CharSequence[] items = {
getString(R.string.select_another_image),
getString(R.string.use_odk_default) };
AlertDialog.Builder builder = new AlertDialog.Builder(
PreferencesActivity.this);
builder.setTitle(getString(R.string.change_splash_path));
builder.setNeutralButton(
getString(R.string.cancel),
new DialogInterface.OnClickListener() {
@Override
public void onClick(
DialogInterface dialog, int id) {
dialog.dismiss();
}
});
builder.setItems(items,
new DialogInterface.OnClickListener() {
@Override
public void onClick(
DialogInterface dialog, int item) {
if (items[item]
.equals(getString(R.string.select_another_image))) {
launchImageChooser();
} else {
setSplashPath(getString(R.string.default_splash_path));
}
}
});
AlertDialog alert = builder.create();
alert.show();
} else {
launchImageChooser();
}
return true;
}
});
mSplashPathPreference.setSummary(mSplashPathPreference
.getSharedPreferences().getString(KEY_SPLASH_PATH,
getString(R.string.default_splash_path)));
boolean showSplashAvailable = adminPreferences.getBoolean(
AdminPreferencesActivity.KEY_SHOW_SPLASH_SCREEN, true);
CheckBoxPreference showSplashPreference = (CheckBoxPreference) findPreference(KEY_SHOW_SPLASH);
if (!(showSplashAvailable || adminMode)) {
clientCategory.removePreference(showSplashPreference);
clientCategory.removePreference(mSplashPathPreference);
}
if (!(fontAvailable || defaultAvailable
|| showSplashAvailable || navigationAvailable || adminMode || resolutionAvailable)) {
getPreferenceScreen().removePreference(clientCategory);
}
}
@Override
protected void onPause() {
super.onPause();
// the property manager should be re-assigned, as properties
// may have changed.
PropertyManager mgr = new PropertyManager(this);
FormController.initializeJavaRosa(mgr);
}
@Override
protected void onResume() {
super.onResume();
// has to go in onResume because it may get updated by
// a sub-preference screen
// this just keeps the widgets in sync
SharedPreferences sp = PreferenceManager
.getDefaultSharedPreferences(this);
String account = sp.getString(KEY_SELECTED_GOOGLE_ACCOUNT, "");
mSelectedGoogleAccountPreference.setSummary(account);
mSelectedGoogleAccountPreference.setValue(account);
String user = sp.getString(KEY_USERNAME, "");
String pw = sp.getString(KEY_PASSWORD, "");
mUsernamePreference.setSummary(user);
mUsernamePreference.setText(user);
if (pw != null && pw.length() > 0) {
mPasswordPreference.setSummary("********");
mPasswordPreference.setText(pw);
}
}
private void setSplashPath(String path) {
SharedPreferences sharedPreferences = PreferenceManager
.getDefaultSharedPreferences(this);
Editor editor = sharedPreferences.edit();
editor.putString(KEY_SPLASH_PATH, path);
editor.commit();
mSplashPathPreference = (PreferenceScreen) findPreference(KEY_SPLASH_PATH);
mSplashPathPreference.setSummary(mSplashPathPreference
.getSharedPreferences().getString(KEY_SPLASH_PATH,
getString(R.string.default_splash_path)));
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode == RESULT_CANCELED) {
// request was canceled, so do nothing
return;
}
switch (requestCode) {
case IMAGE_CHOOSER:
String sourceImagePath = null;
// get gp of chosen file
Uri uri = intent.getData();
if (uri.toString().startsWith("file")) {
sourceImagePath = uri.toString().substring(6);
} else {
String[] projection = { Images.Media.DATA };
Cursor c = null;
try {
c = getContentResolver().query(uri, projection, null, null,
null);
int i = c.getColumnIndexOrThrow(Images.Media.DATA);
c.moveToFirst();
sourceImagePath = c.getString(i);
} finally {
if (c != null) {
c.close();
}
}
}
// setting image path
setSplashPath(sourceImagePath);
break;
}
}
private void setDefaultAggregatePaths() {
SharedPreferences sp = PreferenceManager
.getDefaultSharedPreferences(this);
Editor editor = sp.edit();
editor.putString(KEY_FORMLIST_URL,
getString(R.string.default_odk_formlist));
editor.putString(KEY_SUBMISSION_URL,
getString(R.string.default_odk_submission));
editor.commit();
}
/**
* Disallows carriage returns from user entry
*
* @return
*/
protected InputFilter getReturnFilter() {
InputFilter returnFilter = new InputFilter() {
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
for (int i = start; i < end; i++) {
if (Character.getType((source.charAt(i))) == Character.CONTROL) {
return "";
}
}
return null;
}
};
return returnFilter;
}
/**
* Generic listener that sets the summary to the newly selected/entered
* value
*/
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
preference.setSummary((CharSequence) newValue);
return true;
}
}