package org.commcare.preferences;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.widget.Toast;
import org.commcare.CommCareApp;
import org.commcare.CommCareApplication;
import org.commcare.activities.SessionAwarePreferenceActivity;
import org.commcare.dalvik.BuildConfig;
import org.commcare.dalvik.R;
import org.commcare.google.services.analytics.GoogleAnalyticsFields;
import org.commcare.google.services.analytics.GoogleAnalyticsUtils;
import org.commcare.android.database.user.models.FormRecord;
import org.commcare.logging.AndroidLogger;
import org.commcare.utils.TemplatePrinterUtils;
import org.javarosa.core.services.locale.Localization;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class DeveloperPreferences extends SessionAwarePreferenceActivity
implements SharedPreferences.OnSharedPreferenceChangeListener {
public static final int RESULT_SYNC_CUSTOM = Activity.RESULT_FIRST_USER + 1;
public static final int REQUEST_SYNC_FILE = 1;
// REGION - all Developer Preference keys
public final static String PREFS_CUSTOM_RESTORE_DOC_LOCATION = "cc-custom-restore-doc-location";
public static final String SUPERUSER_ENABLED = "cc-superuser-enabled";
public static final String NAV_UI_ENABLED = "cc-nav-ui-enabled";
public static final String CSS_ENABLED = "cc-css-enabled";
public static final String MARKDOWN_ENABLED = "cc-markdown-enabled";
public static final String ACTION_BAR_ENABLED = "cc-action-nav-enabled";
public static final String LIST_REFRESH_ENABLED = "cc-list-refresh";
public static final String HOME_REPORT_ENABLED = "cc-home-report";
public static final String AUTO_PURGE_ENABLED = "cc-auto-purge";
public static final String LOAD_FORM_PAYLOAD_AS = "cc-form-payload-status";
public static final String DETAIL_TAB_SWIPE_ACTION_ENABLED = "cc-detail-final-swipe-enabled";
public static final String USE_ROOT_MENU_AS_HOME_SCREEN = "cc-use-root-menu-as-home-screen";
public static final String SHOW_ADB_ENTITY_LIST_TRACES = "cc-show-entity-trace-outputs";
public static final String USE_OBFUSCATED_PW = "cc-use-pw-obfuscation";
public static final String ENABLE_BULK_PERFORMANCE = "cc-enable-bulk-performance";
public static final String SHOW_UPDATE_OPTIONS_SETTING = "cc-show-update-target-options";
/**
* Stores last used password and performs auto-login when that password is
* present
*/
public final static String ENABLE_AUTO_LOGIN = "cc-enable-auto-login";
public final static String ENABLE_SAVE_SESSION = "cc-enable-session-saving";
/**
* Stores the navigation and form entry sessions as one string for user manipulation
*/
public final static String EDIT_SAVE_SESSION = "__edit_session_save";
public final static String ALTERNATE_QUESTION_LAYOUT_ENABLED = "cc-alternate-question-text-format";
public final static String OFFER_PIN_FOR_LOGIN = "cc-offer-pin-for-login";
// ENDREGION
private static final Set<String> WHITELISTED_DEVELOPER_PREF_KEYS = new HashSet<>();
static {
WHITELISTED_DEVELOPER_PREF_KEYS.add(SUPERUSER_ENABLED);
WHITELISTED_DEVELOPER_PREF_KEYS.add(SHOW_UPDATE_OPTIONS_SETTING);
WHITELISTED_DEVELOPER_PREF_KEYS.add(AUTO_PURGE_ENABLED);
WHITELISTED_DEVELOPER_PREF_KEYS.add(ALTERNATE_QUESTION_LAYOUT_ENABLED);
}
/**
* Spacer to distinguish between the saved navigation session and form entry session
*/
private static final String NAV_AND_FORM_SESSION_SPACER = "@@@@@";
private static final Map<String, String> prefKeyToAnalyticsEvent = new HashMap<>();
private Preference savedSessionEditTextPreference;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GoogleAnalyticsUtils.reportPrefActivityEntry(GoogleAnalyticsFields.CATEGORY_DEV_PREFS);
PreferenceManager prefMgr = getPreferenceManager();
prefMgr.setSharedPreferencesName((CommCareApplication.instance().getCurrentApp().getPreferencesFilename()));
addPreferencesFromResource(R.xml.preferences_developer);
setTitle("Developer Options");
populatePrefKeyToEventLabelMapping();
GoogleAnalyticsUtils.createPreferenceOnClickListeners(
prefMgr, prefKeyToAnalyticsEvent, GoogleAnalyticsFields.CATEGORY_DEV_PREFS);
savedSessionEditTextPreference = findPreference(EDIT_SAVE_SESSION);
setSessionEditText();
createOnCustomRestoreOption(prefMgr);
hideDangerousDeveloperPrefsIfNeeded();
}
private void createOnCustomRestoreOption(PreferenceManager prefMgr) {
Preference pref = prefMgr.findPreference(PREFS_CUSTOM_RESTORE_DOC_LOCATION);
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
GoogleAnalyticsUtils.reportPrefItemClick(
GoogleAnalyticsFields.CATEGORY_DEV_PREFS,
GoogleAnalyticsFields.LABEL_CUSTOM_RESTORE);
startFileBrowser();
return true;
}
});
}
private void startFileBrowser() {
Intent chooseTemplateIntent = new Intent()
.setAction(Intent.ACTION_GET_CONTENT)
.setType("file/*")
.addCategory(Intent.CATEGORY_OPENABLE);
try {
startActivityForResult(chooseTemplateIntent, REQUEST_SYNC_FILE);
} catch (ActivityNotFoundException e) {
// Means that there is no file browser installed on the device
TemplatePrinterUtils.showAlertDialog(this, "Can't restore custom XML File",
Localization.get("no.file.browser"), false);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_SYNC_FILE) {
if (resultCode == RESULT_OK && data != null) {
this.setResult(DeveloperPreferences.RESULT_SYNC_CUSTOM, data);
this.finish();
} else {
//No file selected
Toast.makeText(this, "No file requested...", Toast.LENGTH_SHORT).show();
}
}
}
private static void populatePrefKeyToEventLabelMapping() {
prefKeyToAnalyticsEvent.put(SUPERUSER_ENABLED, GoogleAnalyticsFields.LABEL_DEV_MODE);
prefKeyToAnalyticsEvent.put(ACTION_BAR_ENABLED, GoogleAnalyticsFields.LABEL_ACTION_BAR);
prefKeyToAnalyticsEvent.put(NAV_UI_ENABLED, GoogleAnalyticsFields.LABEL_NAV_UI);
prefKeyToAnalyticsEvent.put(LIST_REFRESH_ENABLED, GoogleAnalyticsFields.LABEL_ENTITY_LIST_REFRESH);
prefKeyToAnalyticsEvent.put(ENABLE_AUTO_LOGIN, GoogleAnalyticsFields.LABEL_AUTO_LOGIN);
prefKeyToAnalyticsEvent.put(ENABLE_SAVE_SESSION, GoogleAnalyticsFields.LABEL_SESSION_SAVING);
prefKeyToAnalyticsEvent.put(EDIT_SAVE_SESSION, GoogleAnalyticsFields.LABEL_EDIT_SAVED_SESSION);
prefKeyToAnalyticsEvent.put(CSS_ENABLED, GoogleAnalyticsFields.LABEL_CSS);
prefKeyToAnalyticsEvent.put(MARKDOWN_ENABLED, GoogleAnalyticsFields.LABEL_MARKDOWN);
prefKeyToAnalyticsEvent.put(ALTERNATE_QUESTION_LAYOUT_ENABLED, GoogleAnalyticsFields.LABEL_IMAGE_ABOVE_TEXT);
prefKeyToAnalyticsEvent.put(HOME_REPORT_ENABLED, GoogleAnalyticsFields.LABEL_REPORT_BUTTON_ENABLED);
prefKeyToAnalyticsEvent.put(AUTO_PURGE_ENABLED, GoogleAnalyticsFields.LABEL_AUTO_PURGE);
prefKeyToAnalyticsEvent.put(LOAD_FORM_PAYLOAD_AS, GoogleAnalyticsFields.LABEL_LOAD_FORM_PAYLOAD_AS);
prefKeyToAnalyticsEvent.put(DETAIL_TAB_SWIPE_ACTION_ENABLED, GoogleAnalyticsFields.LABEL_DETAIL_TAB_SWIPE_ACTION);
prefKeyToAnalyticsEvent.put(PREFS_CUSTOM_RESTORE_DOC_LOCATION, GoogleAnalyticsFields.LABEL_CUSTOM_RESTORE);
}
private void setSessionEditText() {
if (isSessionSavingEnabled()) {
getPreferenceScreen().addPreference(savedSessionEditTextPreference);
((EditTextPreference)savedSessionEditTextPreference).setText(getSavedSessionStateAsString());
} else {
getPreferenceScreen().removePreference(savedSessionEditTextPreference);
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
String analyticsLabelForPref = prefKeyToAnalyticsEvent.get(key);
if (analyticsLabelForPref != null) {
GoogleAnalyticsUtils.reportEditPref(GoogleAnalyticsFields.CATEGORY_DEV_PREFS,
analyticsLabelForPref, getEditPrefValue(key));
}
switch (key) {
case ENABLE_AUTO_LOGIN:
if (!isAutoLoginEnabled()) {
DevSessionRestorer.clearPassword(sharedPreferences);
}
break;
case EDIT_SAVE_SESSION:
String sessionString =
CommCareApplication.instance().getCurrentApp().getAppPreferences().getString(EDIT_SAVE_SESSION, "");
if (!"".equals(sessionString)) {
setSessionStateFromEditText(sessionString);
}
break;
case ENABLE_SAVE_SESSION:
if (!isSessionSavingEnabled()) {
DevSessionRestorer.clearSession();
}
setSessionEditText();
break;
case LOAD_FORM_PAYLOAD_AS:
if (!formLoadPayloadStatus().equals(FormRecord.STATUS_SAVED)) {
// clear submission server so that 'unsent' forms that are loaded don't get sent to HQ
CommCareApplication.instance().getCurrentApp().getAppPreferences().edit()
.putString(CommCareServerPreferences.PREFS_SUBMISSION_URL_KEY, "")
.apply();
}
break;
}
}
private static String getSavedSessionStateAsString() {
SharedPreferences prefs = CommCareApplication.instance().getCurrentApp().getAppPreferences();
String navSession = prefs.getString(CommCarePreferences.CURRENT_SESSION, "");
String formEntrySession = prefs.getString(CommCarePreferences.CURRENT_FORM_ENTRY_SESSION, "");
if ("".equals(navSession) && "".equals(formEntrySession)) {
return "";
} else {
return navSession + NAV_AND_FORM_SESSION_SPACER + formEntrySession;
}
}
private static void setSessionStateFromEditText(String sessionString) {
SharedPreferences.Editor editor =
CommCareApplication.instance().getCurrentApp().getAppPreferences().edit();
String[] sessionParts = sessionString.split(NAV_AND_FORM_SESSION_SPACER);
editor.putString(CommCarePreferences.CURRENT_SESSION, sessionParts[0]);
editor.putString(CommCarePreferences.CURRENT_FORM_ENTRY_SESSION, sessionParts[1]);
editor.commit();
}
private static int getEditPrefValue(String key) {
if (CommCareApplication.instance().getCurrentApp().getAppPreferences().
getString(key, CommCarePreferences.NO).equals(CommCarePreferences.YES)) {
return GoogleAnalyticsFields.VALUE_ENABLED;
} else {
return GoogleAnalyticsFields.VALUE_DISABLED;
}
}
@Override
protected void onResume() {
super.onResume();
getPreferenceScreen().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
}
/**
* Try to lookup key in app preferences and test equality of the result to
* matchingValue. If either the app or preference key don't exist, just
* compare defaultValue to matchingValue
*
* @param key is a potential entry in the app preferences
* @param defaultValue use this value if key not found
* @param matchingValue compare this to key lookup or defaultValue
* @return boolean
*/
private static boolean doesPropertyMatch(String key, String defaultValue, String matchingValue) {
CommCareApp app = CommCareApplication.instance().getCurrentApp();
if (app == null) {
return defaultValue.equals(matchingValue);
}
SharedPreferences properties = app.getAppPreferences();
return properties.getString(key, defaultValue).equals(matchingValue);
}
/**
* Lookup superuser preference; if debug build, superuser is enabled by
* default.
*
* @return is the superuser developer preference enabled?
*/
public static boolean isSuperuserEnabled() {
return doesPropertyMatch(SUPERUSER_ENABLED, BuildConfig.DEBUG ? CommCarePreferences.YES : CommCarePreferences.NO, CommCarePreferences.YES);
}
public static boolean isActionBarEnabled() {
return doesPropertyMatch(ACTION_BAR_ENABLED, CommCarePreferences.YES, CommCarePreferences.YES);
}
public static boolean isNewNavEnabled() {
SharedPreferences properties = CommCareApplication.instance().getCurrentApp().getAppPreferences();
return properties.getString(NAV_UI_ENABLED, CommCarePreferences.YES).equals(CommCarePreferences.YES);
}
public static boolean isCssEnabled() {
return doesPropertyMatch(CSS_ENABLED, CommCarePreferences.NO, CommCarePreferences.YES);
}
public static boolean isListRefreshEnabled() {
SharedPreferences properties = CommCareApplication.instance().getCurrentApp().getAppPreferences();
return properties.getString(LIST_REFRESH_ENABLED, CommCarePreferences.NO).equals(CommCarePreferences.YES);
}
public static boolean isAutoLoginEnabled() {
SharedPreferences properties = CommCareApplication.instance().getCurrentApp().getAppPreferences();
return properties.getString(ENABLE_AUTO_LOGIN, CommCarePreferences.NO).equals(CommCarePreferences.YES);
}
public static boolean isSessionSavingEnabled() {
if (CommCareApplication.instance().getCurrentApp() == null) {
// null check needed for corner case in robolectric tests
return false;
} else {
SharedPreferences properties = CommCareApplication.instance().getCurrentApp().getAppPreferences();
return properties.getString(ENABLE_SAVE_SESSION, CommCarePreferences.NO).
equals(CommCarePreferences.YES);
}
}
public static void enableSessionSaving() {
CommCareApplication.instance().getCurrentApp().getAppPreferences()
.edit()
.putString(DeveloperPreferences.ENABLE_SAVE_SESSION, CommCarePreferences.YES)
.apply();
}
public static boolean isMarkdownEnabled() {
return doesPropertyMatch(MARKDOWN_ENABLED, CommCarePreferences.NO, CommCarePreferences.YES);
}
public static boolean imageAboveTextEnabled() {
return doesPropertyMatch(ALTERNATE_QUESTION_LAYOUT_ENABLED, CommCarePreferences.NO,
CommCarePreferences.YES);
}
public static boolean isHomeReportEnabled() {
return doesPropertyMatch(HOME_REPORT_ENABLED, CommCarePreferences.NO,
CommCarePreferences.YES);
}
public static boolean shouldOfferPinForLogin() {
return doesPropertyMatch(OFFER_PIN_FOR_LOGIN, CommCarePreferences.NO,
CommCarePreferences.YES);
}
public static boolean isAutoPurgeEnabled() {
return doesPropertyMatch(AUTO_PURGE_ENABLED, CommCarePreferences.NO,
CommCarePreferences.YES);
}
public static String formLoadPayloadStatus() {
SharedPreferences properties = CommCareApplication.instance().getCurrentApp().getAppPreferences();
return properties.getString(LOAD_FORM_PAYLOAD_AS, FormRecord.STATUS_SAVED);
}
/**
* Feature flag to control whether swiping in case detail tabs can trigger
* exit from the case detail screen
*/
public static boolean isDetailTabSwipeActionEnabled() {
return doesPropertyMatch(DETAIL_TAB_SWIPE_ACTION_ENABLED, CommCarePreferences.YES, CommCarePreferences.YES);
}
public static boolean useRootModuleMenuAsHomeScreen() {
return doesPropertyMatch(USE_ROOT_MENU_AS_HOME_SCREEN, CommCarePreferences.NO, CommCarePreferences.YES);
}
public static boolean collectAndDisplayEntityTrances() {
return doesPropertyMatch(SHOW_ADB_ENTITY_LIST_TRACES, CommCarePreferences.NO, CommCarePreferences.YES);
}
public static boolean useObfuscatedPassword() {
return doesPropertyMatch(USE_OBFUSCATED_PW, CommCarePreferences.NO, CommCarePreferences.YES);
}
public static boolean isBulkPerformanceEnabled() {
return doesPropertyMatch(ENABLE_BULK_PERFORMANCE, CommCarePreferences.NO, CommCarePreferences.YES);
}
public static boolean shouldShowUpdateOptionsSetting() {
return doesPropertyMatch(SHOW_UPDATE_OPTIONS_SETTING, CommCarePreferences.NO,
CommCarePreferences.YES) || BuildConfig.DEBUG;
}
private void hideDangerousDeveloperPrefsIfNeeded() {
if (!GlobalPrivilegesManager.isAdvancedSettingsAccessEnabled() && !BuildConfig.DEBUG) {
// Dangerous privileges should not be showing
PreferenceScreen prefScreen = getPreferenceScreen();
for (Preference p : getOnScreenPrefs()) {
if (p != null && !WHITELISTED_DEVELOPER_PREF_KEYS.contains(p.getKey())) {
prefScreen.removePreference(p);
}
}
}
}
private Preference[] getOnScreenPrefs() {
PreferenceScreen prefScreen = getPreferenceScreen();
Preference[] prefs = new Preference[prefScreen.getPreferenceCount()];
for (int i = 0; i < prefScreen.getPreferenceCount(); i++) {
prefs[i] = prefScreen.getPreference(i);
}
return prefs;
}
}