com.ichi2.anki.PreferenceContext.java Source code

Java tutorial

Introduction

Here is the source code for com.ichi2.anki.PreferenceContext.java

Source

/***************************************************************************************
 * Copyright (c) 2009 Nicolas Raoul <nicolas.raoul@gmail.com>                           *
 * Copyright (c) 2009 Edu Zamora <edu.zasu@gmail.com>                                   *
 * Copyright (c) 2010 Norbert Nagold <norbert.nagold@gmail.com>                         *
 * Copyright (c) 2012 Kostas Spyropoulos <inigo.aldana@gmail.com>                       *
 * Copyright (c) 2015 Timothy Rae <perceptualchaos2@gmail.com>                          *
 *                                                                                      *
 * This program 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.                                                                             *
 *                                                                                      *
 * 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 General Public License for more details.             *
 *                                                                                      *
 * You should have received a copy of the GNU General Public License along with         *
 * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
 ****************************************************************************************/

package com.ichi2.anki;

import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
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.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.support.annotation.NonNull;
import android.preference.PreferenceScreen;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.WindowManager.BadTokenException;
import android.widget.Toast;

import com.afollestad.materialdialogs.MaterialDialog;
import com.ichi2.themes.Themes;
import com.ichi2.ui.AppCompatPreferenceActivity;
import com.ichi2.ui.SeekBarPreference;
import com.ichi2.anim.ActivityTransitionAnimation;
import com.ichi2.anki.exception.StorageAccessException;
import com.ichi2.compat.CompatHelper;
import com.ichi2.libanki.Collection;
import com.ichi2.libanki.Utils;
import com.ichi2.libanki.hooks.ChessFilter;
import com.ichi2.libanki.hooks.HebrewFixFilter;
import com.ichi2.libanki.hooks.Hooks;
import com.ichi2.preferences.NumberRangePreference;
import com.ichi2.utils.LanguageUtil;
import com.ichi2.utils.VersionUtils;

import org.json.JSONException;
import org.json.JSONObject;

import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;

import timber.log.Timber;

interface PreferenceContext {
    void addPreferencesFromResource(int preferencesResId);

    PreferenceScreen getPreferenceScreen();
}

/**
 * Preferences dialog.
 */
public class Preferences extends AppCompatPreferenceActivity
        implements PreferenceContext, OnSharedPreferenceChangeListener {

    private static final int DIALOG_HEBREW_FONT = 3;
    public static boolean COMING_FROM_ADD = false;

    /** Key of the language preference */
    public static final String LANGUAGE = "language";

    // Other variables
    private final HashMap<String, String> mOriginalSumarries = new HashMap<>();
    private static final String[] sCollectionPreferences = { "showEstimates", "showProgress", "learnCutoff",
            "timeLimit", "useCurrent", "newSpread", "dayOffset" };

    // ----------------------------------------------------------------------------
    // Overridden methods
    // ----------------------------------------------------------------------------

    @SuppressWarnings("deprecation")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Themes.setThemeLegacy(this);

        super.onCreate(savedInstanceState);

        // Legacy code using intents instead of PreferenceFragments
        String action = getIntent().getAction();
        if (!CompatHelper.isHoneycomb()) {
            if (action == null) {
                // Headers screen
                addPreferencesFromResource(R.xml.preference_headers_legacy);
            } else {
                initSubscreen(action, this);
            }
            // Set the text for the summary of each of the preferences
            initAllPreferences(getPreferenceScreen());
        }

        // Add a home button to the actionbar
        getSupportActionBar().setHomeButtonEnabled(true);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    }

    private Collection getCol() {
        return CollectionHelper.getInstance().getCol(this);
    }

    // Called only on Honeycomb and later
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @Override
    public void onBuildHeaders(List<Header> target) {
        loadHeadersFromResource(R.xml.preference_headers, target);
    }

    @Override
    protected boolean isValidFragment(String fragmentName) {
        return SettingsFragment.class.getName().equals(fragmentName);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case android.R.id.home:
            finish();
            return true;
        }
        return false;
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void onPause() {
        super.onPause();
        // Legacy code to register listener when not using PreferenceFragment
        if (!CompatHelper.isHoneycomb() && getPreferenceScreen() != null) {
            getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
        }
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void onResume() {
        super.onResume();
        // Legacy code to register listener when not using PreferenceFragment
        if (!CompatHelper.isHoneycomb() && getPreferenceScreen() != null) {
            SharedPreferences prefs = getPreferenceScreen().getSharedPreferences();
            prefs.registerOnSharedPreferenceChangeListener(this);
            // syncAccount's summary can change while preferences are still open (user logs
            // in from preferences screen), so we need to update it here.
            updatePreference(prefs, "syncAccount", this);
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
            Timber.i("Preferences:: onBackPressed()");
            closePreferences();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    protected MaterialDialog onCreateDialog(int id) {
        Resources res = getResources();
        MaterialDialog.Builder builder = new MaterialDialog.Builder(this);
        switch (id) {
        case DIALOG_HEBREW_FONT:
            builder.title(res.getString(R.string.fix_hebrew_text));
            builder.content(res.getString(R.string.fix_hebrew_instructions,
                    CollectionHelper.getCurrentAnkiDroidDirectory(this)));
            builder.callback(new MaterialDialog.ButtonCallback() {
                @Override
                public void onPositive(MaterialDialog dialog) {
                    Intent intent = new Intent("android.intent.action.VIEW",
                            Uri.parse(getResources().getString(R.string.link_hebrew_font)));
                    startActivity(intent);
                }
            });
            builder.positiveText(res.getString(R.string.fix_hebrew_download_font));
            builder.negativeText(R.string.dialog_cancel);
        }
        return builder.show();
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        updatePreference(sharedPreferences, key, this);
    }

    // ----------------------------------------------------------------------------
    // Class methods
    // ----------------------------------------------------------------------------

    private void initSubscreen(String action, PreferenceContext listener) {
        PreferenceScreen screen;
        switch (action) {
        case "com.ichi2.anki.prefs.general":
            listener.addPreferencesFromResource(R.xml.preferences_general);
            screen = listener.getPreferenceScreen();
            // Build languages
            initializeLanguageDialog(screen);
            break;
        case "com.ichi2.anki.prefs.reviewing":
            listener.addPreferencesFromResource(R.xml.preferences_reviewing);
            screen = listener.getPreferenceScreen();
            // Show error toast if the user tries to disable answer button without gestures on
            ListPreference fullscreenPreference = (ListPreference) screen.findPreference("fullscreenMode");
            fullscreenPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
                public boolean onPreferenceChange(Preference preference, final Object newValue) {
                    SharedPreferences prefs = AnkiDroidApp.getSharedPrefs(Preferences.this);
                    if (prefs.getBoolean("gestures", false) || !newValue.equals("2")) {
                        return true;
                    } else {
                        Toast.makeText(getApplicationContext(), R.string.full_screen_error_gestures,
                                Toast.LENGTH_LONG).show();
                        return false;
                    }
                }
            });
            break;
        case "com.ichi2.anki.prefs.fonts":
            listener.addPreferencesFromResource(R.xml.preferences_fonts);
            screen = listener.getPreferenceScreen();
            initializeCustomFontsDialog(screen);
            break;
        case "com.ichi2.anki.prefs.gestures":
            listener.addPreferencesFromResource(R.xml.preferences_gestures);
            break;
        case "com.ichi2.anki.prefs.advanced":
            listener.addPreferencesFromResource(R.xml.preferences_advanced);
            screen = listener.getPreferenceScreen();
            // Check that input is valid before committing change in the collection path
            EditTextPreference collectionPathPreference = (EditTextPreference) screen.findPreference("deckPath");
            collectionPathPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
                public boolean onPreferenceChange(Preference preference, final Object newValue) {
                    final String newPath = (String) newValue;
                    try {
                        CollectionHelper.initializeAnkiDroidDirectory(newPath);
                        return true;
                    } catch (StorageAccessException e) {
                        Timber.e(e, "Could not initialize directory: %s", newPath);
                        Toast.makeText(getApplicationContext(), R.string.dialog_collection_path_not_dir,
                                Toast.LENGTH_LONG).show();
                        return false;
                    }
                }
            });
            // Force full sync option
            Preference fullSyncPreference = screen.findPreference("force_full_sync");
            fullSyncPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
                public boolean onPreferenceClick(Preference preference) {
                    // TODO: Could be useful to show the full confirmation dialog
                    getCol().modSchemaNoCheck();
                    getCol().setMod();
                    Toast.makeText(getApplicationContext(), android.R.string.ok, Toast.LENGTH_SHORT).show();
                    return true;
                }
            });
            // Workaround preferences
            removeUnnecessaryAdvancedPrefs(screen);
            break;
        }
    }

    /**
     * Loop over every preference in the list and set the summary text
     */
    private void initAllPreferences(PreferenceScreen screen) {
        for (int i = 0; i < screen.getPreferenceCount(); ++i) {
            Preference preference = screen.getPreference(i);
            if (preference instanceof PreferenceGroup) {
                PreferenceGroup preferenceGroup = (PreferenceGroup) preference;
                for (int j = 0; j < preferenceGroup.getPreferenceCount(); ++j) {
                    Preference nestedPreference = preferenceGroup.getPreference(j);
                    if (nestedPreference instanceof PreferenceGroup) {
                        PreferenceGroup nestedPreferenceGroup = (PreferenceGroup) nestedPreference;
                        for (int k = 0; k < nestedPreferenceGroup.getPreferenceCount(); ++k) {
                            initPreference(nestedPreferenceGroup.getPreference(k));
                        }
                    } else {
                        initPreference(preferenceGroup.getPreference(j));
                    }
                }
            } else {
                initPreference(preference);
            }
        }
    }

    private void initPreference(Preference pref) {
        // Load stored values from Preferences which are stored in the Collection
        if (Arrays.asList(sCollectionPreferences).contains(pref.getKey())) {
            Collection col = getCol();
            if (col != null) {
                try {
                    JSONObject conf = col.getConf();
                    switch (pref.getKey()) {
                    case "showEstimates":
                        ((CheckBoxPreference) pref).setChecked(conf.getBoolean("estTimes"));
                        break;
                    case "showProgress":
                        ((CheckBoxPreference) pref).setChecked(conf.getBoolean("dueCounts"));
                        break;
                    case "learnCutoff":
                        ((NumberRangePreference) pref).setValue(conf.getInt("collapseTime") / 60);
                        break;
                    case "timeLimit":
                        ((NumberRangePreference) pref).setValue(conf.getInt("timeLim") / 60);
                        break;
                    case "useCurrent":
                        ((ListPreference) pref).setValueIndex(conf.optBoolean("addToCur", true) ? 0 : 1);
                        break;
                    case "newSpread":
                        ((ListPreference) pref).setValueIndex(conf.getInt("newSpread"));
                        break;
                    case "dayOffset":
                        Calendar calendar = new GregorianCalendar();
                        Timestamp timestamp = new Timestamp(col.getCrt() * 1000);
                        calendar.setTimeInMillis(timestamp.getTime());
                        ((SeekBarPreference) pref).setValue(calendar.get(Calendar.HOUR_OF_DAY));
                        break;
                    }
                } catch (JSONException | NumberFormatException e) {
                    throw new RuntimeException();
                }
            } else {
                // Disable Col preferences if Collection closed
                pref.setEnabled(false);
            }
        } else if ("minimumCardsDueForNotification".equals(pref.getKey())) {
            updateNotificationPreference((ListPreference) pref);
        }
        // Set the value from the summary cache
        CharSequence s = pref.getSummary();
        mOriginalSumarries.put(pref.getKey(), (s != null) ? s.toString() : "");
        // Update summary

        updateSummary(pref);
    }

    /**
     * Code which is run when a SharedPreference change has been detected
     * @param prefs instance of SharedPreferences
     * @param key key in prefs which is being updated
     * @param listener PreferenceActivity of PreferenceFragment which is hosting the preference
     */
    private void updatePreference(SharedPreferences prefs, String key, PreferenceContext listener) {
        try {
            PreferenceScreen screen = listener.getPreferenceScreen();
            Preference pref = screen.findPreference(key);
            // Handle special cases
            switch (key) {
            case "timeoutAnswer": {
                CheckBoxPreference keepScreenOn = (CheckBoxPreference) screen.findPreference("keepScreenOn");
                keepScreenOn.setChecked(((CheckBoxPreference) pref).isChecked());
                break;
            }
            case LANGUAGE:
                closePreferences();
                break;
            case "convertFenText":
                if (((CheckBoxPreference) pref).isChecked()) {
                    ChessFilter.install(Hooks.getInstance(getApplicationContext()));
                } else {
                    ChessFilter.uninstall(Hooks.getInstance(getApplicationContext()));
                }
                break;
            case "fixHebrewText":
                if (((CheckBoxPreference) pref).isChecked()) {
                    HebrewFixFilter.install(Hooks.getInstance(getApplicationContext()));
                    showDialog(DIALOG_HEBREW_FONT);
                } else {
                    HebrewFixFilter.uninstall(Hooks.getInstance(getApplicationContext()));
                }
                break;
            case "showProgress":
                getCol().getConf().put("dueCounts", ((CheckBoxPreference) pref).isChecked());
                getCol().setMod();
                break;
            case "showEstimates":
                getCol().getConf().put("estTimes", ((CheckBoxPreference) pref).isChecked());
                getCol().setMod();
                break;
            case "newSpread":
                getCol().getConf().put("newSpread", Integer.parseInt(((ListPreference) pref).getValue()));
                getCol().setMod();
                break;
            case "timeLimit":
                getCol().getConf().put("timeLim", ((NumberRangePreference) pref).getValue() * 60);
                getCol().setMod();
                break;
            case "learnCutoff":
                getCol().getConf().put("collapseTime", ((NumberRangePreference) pref).getValue() * 60);
                getCol().setMod();
                break;
            case "useCurrent":
                getCol().getConf().put("addToCur", ((ListPreference) pref).getValue().equals("0"));
                getCol().setMod();
                break;
            case "dayOffset": {
                int hours = ((SeekBarPreference) pref).getValue();
                Timestamp crtTime = new Timestamp(getCol().getCrt() * 1000);
                Calendar date = GregorianCalendar.getInstance();
                date.setTimeInMillis(crtTime.getTime());
                date.set(Calendar.HOUR_OF_DAY, hours);
                getCol().setCrt(date.getTimeInMillis() / 1000);
                getCol().setMod();
                break;
            }
            case "minimumCardsDueForNotification": {
                ListPreference listpref = (ListPreference) screen.findPreference("minimumCardsDueForNotification");
                if (listpref != null) {
                    updateNotificationPreference(listpref);
                }
                break;
            }
            case "reportErrorMode": {
                String value = prefs.getString("reportErrorMode", "");
                AnkiDroidApp.getInstance().setAcraReportingMode(value);
                break;
            }
            case "syncAccount": {
                SharedPreferences preferences = AnkiDroidApp.getSharedPrefs(getBaseContext());
                String username = preferences.getString("username", "");
                Preference syncAccount = screen.findPreference("syncAccount");
                if (syncAccount != null) {
                    if (TextUtils.isEmpty(username)) {
                        syncAccount.setSummary(R.string.sync_account_summ_logged_out);
                    } else {
                        syncAccount.setSummary(getString(R.string.sync_account_summ_logged_in, username));
                    }
                }
                break;
            }
            case "providerEnabled": {
                ComponentName providerName = new ComponentName(this, "com.ichi2.anki.provider.CardContentProvider");
                PackageManager pm = getPackageManager();
                int state;
                if (((CheckBoxPreference) pref).isChecked()) {
                    state = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
                    Timber.i("AnkiDroid ContentProvider enabled by user");
                } else {
                    state = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
                    Timber.i("AnkiDroid ContentProvider disabled by user");
                }
                pm.setComponentEnabledSetting(providerName, state, PackageManager.DONT_KILL_APP);
                break;
            }
            }
            // Update the summary text to reflect new value
            updateSummary(pref);
        } catch (BadTokenException e) {
            Timber.e(e, "Preferences: BadTokenException on showDialog");
        } catch (NumberFormatException | JSONException e) {
            throw new RuntimeException();
        }
    }

    public void updateNotificationPreference(ListPreference listpref) {
        CharSequence[] entries = listpref.getEntries();
        CharSequence[] values = listpref.getEntryValues();
        for (int i = 0; i < entries.length; i++) {
            int value = Integer.parseInt(values[i].toString());
            if (entries[i].toString().contains("%d")) {
                entries[i] = String.format(entries[i].toString(), value);
            }
        }
        listpref.setEntries(entries);
        listpref.setSummary(listpref.getEntry().toString());
    }

    private void updateSummary(Preference pref) {
        if (pref == null || pref.getKey() == null) {
            return;
        }
        if (pref.getKey().equals("about_dialog_preference")) {
            pref.setSummary(
                    getResources().getString(R.string.about_version) + " " + VersionUtils.getPkgVersionName());
        }
        // Get value text
        String value;
        try {
            if (pref instanceof NumberRangePreference) {
                value = Integer.toString(((NumberRangePreference) pref).getValue());
            } else if (pref instanceof SeekBarPreference) {
                value = Integer.toString(((SeekBarPreference) pref).getValue());
            } else if (pref instanceof ListPreference) {
                value = ((ListPreference) pref).getEntry().toString();
            } else if (pref instanceof EditTextPreference) {
                value = ((EditTextPreference) pref).getText();
            } else {
                return;
            }
        } catch (NullPointerException e) {
            value = "";
        }
        // Get summary text
        String oldSummary = mOriginalSumarries.get(pref.getKey());
        // Replace summary text with value according to some rules
        if (oldSummary.equals("")) {
            pref.setSummary(value);
        } else if (value.equals("")) {
            pref.setSummary(oldSummary);
        } else if (pref.getKey().equals("minimumCardsDueForNotification")) {
            pref.setSummary(replaceStringIfNumeric(oldSummary, value));
        } else {
            pref.setSummary(replaceString(oldSummary, value));
        }
    }

    private String replaceString(String str, String value) {
        if (str.contains("XXX")) {
            return str.replace("XXX", value);
        } else {
            return str;
        }
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    private String replaceStringIfNumeric(String str, String value) {
        try {
            Double.parseDouble(value);
            return replaceString(str, value);
        } catch (NumberFormatException e) {
            return value;
        }
    }

    private void initializeLanguageDialog(PreferenceScreen screen) {
        ListPreference languageSelection = (ListPreference) screen.findPreference(LANGUAGE);
        if (languageSelection != null) {
            Map<String, String> items = new TreeMap<>();
            for (String localeCode : LanguageUtil.APP_LANGUAGES) {
                Locale loc = LanguageUtil.getLocale(localeCode);
                items.put(loc.getDisplayName(), loc.toString());
            }
            CharSequence[] languageDialogLabels = new CharSequence[items.size() + 1];
            CharSequence[] languageDialogValues = new CharSequence[items.size() + 1];
            languageDialogLabels[0] = getResources().getString(R.string.language_system);
            languageDialogValues[0] = "";
            int i = 1;
            for (Map.Entry<String, String> e : items.entrySet()) {
                languageDialogLabels[i] = e.getKey();
                languageDialogValues[i] = e.getValue();
                i++;
            }

            languageSelection.setEntries(languageDialogLabels);
            languageSelection.setEntryValues(languageDialogValues);
        }
    }

    private void removeUnnecessaryAdvancedPrefs(PreferenceScreen screen) {
        PreferenceCategory plugins = (PreferenceCategory) screen.findPreference("category_plugins");
        // Disable the emoji/kana buttons to scroll preference if those keys don't exist
        if (!CompatHelper.hasKanaAndEmojiKeys()) {
            CheckBoxPreference emojiScrolling = (CheckBoxPreference) screen.findPreference("scrolling_buttons");
            if (emojiScrolling != null && plugins != null) {
                plugins.removePreference(emojiScrolling);
            }
        }
        // Disable the double scroll preference if no scrolling keys
        if (!CompatHelper.hasScrollKeys() && !CompatHelper.hasKanaAndEmojiKeys()) {
            CheckBoxPreference doubleScrolling = (CheckBoxPreference) screen.findPreference("double_scrolling");
            if (doubleScrolling != null && plugins != null) {
                plugins.removePreference(doubleScrolling);
            }
        }

        PreferenceCategory workarounds = (PreferenceCategory) screen.findPreference("category_workarounds");
        if (workarounds != null) {
            CheckBoxPreference writeAnswersDisable = (CheckBoxPreference) screen
                    .findPreference("writeAnswersDisable");
            CheckBoxPreference useInputTag = (CheckBoxPreference) screen.findPreference("useInputTag");
            CheckBoxPreference inputWorkaround = (CheckBoxPreference) screen.findPreference("inputWorkaround");
            CheckBoxPreference longclickWorkaround = (CheckBoxPreference) screen
                    .findPreference("textSelectionLongclickWorkaround");
            CheckBoxPreference fixHebrewText = (CheckBoxPreference) screen.findPreference("fixHebrewText");
            CheckBoxPreference safeDisplayMode = (CheckBoxPreference) screen.findPreference("safeDisplay");
            CompatHelper.removeHiddenPreferences(this.getApplicationContext());
            if (CompatHelper.isHoneycomb()) {
                workarounds.removePreference(longclickWorkaround);
            }
            if (CompatHelper.getSdkVersion() >= 13) {
                workarounds.removePreference(safeDisplayMode);
            }
            if (CompatHelper.getSdkVersion() >= 15) {
                workarounds.removePreference(writeAnswersDisable);
                workarounds.removePreference(inputWorkaround);
            } else {
                // For older Androids we never use the input tag anyway.
                workarounds.removePreference(useInputTag);
            }
            if (CompatHelper.getSdkVersion() >= 16) {
                workarounds.removePreference(fixHebrewText);
            }
        }
    }

    /** Initializes the list of custom fonts shown in the preferences. */
    private void initializeCustomFontsDialog(PreferenceScreen screen) {
        ListPreference defaultFontPreference = (ListPreference) screen.findPreference("defaultFont");
        if (defaultFontPreference != null) {
            defaultFontPreference.setEntries(getCustomFonts("System default"));
            defaultFontPreference.setEntryValues(getCustomFonts(""));
        }
        ListPreference browserEditorCustomFontsPreference = (ListPreference) screen
                .findPreference("browserEditorFont");
        browserEditorCustomFontsPreference.setEntries(getCustomFonts("System default"));
        browserEditorCustomFontsPreference.setEntryValues(getCustomFonts("", true));
    }

    /** Returns a list of the names of the installed custom fonts. */
    private String[] getCustomFonts(String defaultValue) {
        return getCustomFonts(defaultValue, false);
    }

    private String[] getCustomFonts(String defaultValue, boolean useFullPath) {
        List<AnkiFont> mFonts = Utils.getCustomFonts(this);
        int count = mFonts.size();
        Timber.d("There are %d custom fonts", count);
        String[] names = new String[count + 1];
        names[0] = defaultValue;
        if (useFullPath) {
            for (int index = 1; index < count + 1; ++index) {
                names[index] = mFonts.get(index - 1).getPath();
                Timber.d("Adding custom font: %s", names[index]);
            }
        } else {
            for (int index = 1; index < count + 1; ++index) {
                names[index] = mFonts.get(index - 1).getName();
                Timber.d("Adding custom font: %s", names[index]);
            }
        }
        return names;
    }

    private void closePreferences() {
        finish();
        ActivityTransitionAnimation.slide(this, ActivityTransitionAnimation.FADE);
        if (getCol() != null && getCol().getDb() != null) {
            getCol().save();
        }
    }

    // ----------------------------------------------------------------------------
    // Inner classes
    // ----------------------------------------------------------------------------

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public static class SettingsFragment extends PreferenceFragment
            implements PreferenceContext, OnSharedPreferenceChangeListener {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            String subscreen = getArguments().getString("subscreen");
            ((Preferences) getActivity()).initSubscreen(subscreen, this);
            ((Preferences) getActivity()).initAllPreferences(getPreferenceScreen());
        }

        @Override
        public void onResume() {
            super.onResume();
            SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
            prefs.registerOnSharedPreferenceChangeListener(this);
            // syncAccount's summary can change while preferences are still open (user logs
            // in from preferences screen), so we need to update it here.
            ((Preferences) getActivity()).updatePreference(prefs, "syncAccount", this);
        }

        @Override
        public void onPause() {
            getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
            super.onPause();
        }

        @Override
        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
            ((Preferences) getActivity()).updatePreference(sharedPreferences, key, this);
        }
    }
}