com.hughes.android.dictionary.DictionaryApplication.java Source code

Java tutorial

Introduction

Here is the source code for com.hughes.android.dictionary.DictionaryApplication.java

Source

// Copyright 2011 Google Inc. All Rights Reserved.
//
// 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 com.hughes.android.dictionary;

import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.support.v4.view.MenuItemCompat;
import android.util.Log;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.Toast;

import com.hughes.android.dictionary.DictionaryInfo.IndexInfo;
import com.hughes.android.dictionary.engine.Dictionary;
import com.hughes.android.dictionary.engine.Language;
import com.hughes.android.dictionary.engine.Language.LanguageResources;
import com.hughes.android.dictionary.engine.TransliteratorManager;
import com.hughes.android.util.PersistentObjectCache;
import com.hughes.util.ListUtil;
import java.text.Collator;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

public class DictionaryApplication extends Application {

    static final String LOG = "QuickDicApp";

    // If set to false, avoid use of ICU collator
    // Works well enough for most european languages,
    // gives faster startup and avoids crashes on some
    // devices due to Dalvik bugs (e.g. ARMv6, S5570i, CM11)
    // when using ICU4J.
    // Leave it enabled by default for correctness except
    // for my known broken development/performance test device config.
    //static public final boolean USE_COLLATOR = !android.os.Build.FINGERPRINT.equals("Samsung/cm_tassve/tassve:4.4.4/KTU84Q/20150211:userdebug/release-keys");
    static public final boolean USE_COLLATOR = true;

    // Static, determined by resources (and locale).
    // Unordered.
    static Map<String, DictionaryInfo> DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO = null;

    enum Theme {
        DEFAULT(R.style.Theme_Default, R.style.Theme_Default_TokenRow_Fg, R.color.theme_default_token_row_fg,
                R.drawable.theme_default_token_row_main_bg, R.drawable.theme_default_token_row_other_bg,
                R.drawable.theme_default_normal_row_bg),

        LIGHT(R.style.Theme_Light, R.style.Theme_Light_TokenRow_Fg, R.color.theme_light_token_row_fg,
                R.drawable.theme_light_token_row_main_bg, R.drawable.theme_light_token_row_other_bg,
                R.drawable.theme_light_normal_row_bg);

        Theme(final int themeId, final int tokenRowFg, final int tokenRowFgColor, final int tokenRowMainBg,
                final int tokenRowOtherBg, final int normalRowBg) {
            this.themeId = themeId;
            this.tokenRowFg = tokenRowFg;
            this.tokenRowFgColor = tokenRowFgColor;
            this.tokenRowMainBg = tokenRowMainBg;
            this.tokenRowOtherBg = tokenRowOtherBg;
            this.normalRowBg = normalRowBg;
        }

        final int themeId;
        final int tokenRowFg;
        final int tokenRowFgColor;
        final int tokenRowMainBg;
        final int tokenRowOtherBg;
        final int normalRowBg;
    }

    // Useful:
    // http://www.loc.gov/standards/iso639-2/php/code_list.php
    public static final Map<String, LanguageResources> isoCodeToResources = new LinkedHashMap<String, LanguageResources>();
    static {
        isoCodeToResources.put("AF",
                new LanguageResources("Afrikaans", R.string.AF, R.drawable.flag_of_south_africa));
        isoCodeToResources.put("SQ", new LanguageResources("Albanian", R.string.SQ, R.drawable.flag_of_albania));
        isoCodeToResources.put("AR", new LanguageResources("Arabic", R.string.AR, R.drawable.arabic));
        isoCodeToResources.put("HY", new LanguageResources("Armenian", R.string.HY, R.drawable.flag_of_armenia));
        isoCodeToResources.put("BE", new LanguageResources("Belarusian", R.string.BE, R.drawable.flag_of_belarus));
        isoCodeToResources.put("BN", new LanguageResources("Bengali", R.string.BN));
        isoCodeToResources.put("BS",
                new LanguageResources("Bosnian", R.string.BS, R.drawable.flag_of_bosnia_and_herzegovina));
        isoCodeToResources.put("BG", new LanguageResources("Bulgarian", R.string.BG, R.drawable.flag_of_bulgaria));
        isoCodeToResources.put("MY", new LanguageResources("Burmese", R.string.MY, R.drawable.flag_of_myanmar));
        isoCodeToResources.put("ZH",
                new LanguageResources("Chinese", R.string.ZH, R.drawable.flag_of_the_peoples_republic_of_china));
        isoCodeToResources.put("cmn",
                new LanguageResources("Mandarin", R.string.cmn, R.drawable.flag_of_the_peoples_republic_of_china));
        isoCodeToResources.put("yue",
                new LanguageResources("Cantonese", R.string.yue, R.drawable.flag_of_hong_kong));
        isoCodeToResources.put("CA", new LanguageResources("Catalan", R.string.CA));
        isoCodeToResources.put("HR", new LanguageResources("Croatian", R.string.HR, R.drawable.flag_of_croatia));
        isoCodeToResources.put("CS",
                new LanguageResources("Czech", R.string.CS, R.drawable.flag_of_the_czech_republic));
        isoCodeToResources.put("DA", new LanguageResources("Danish", R.string.DA, R.drawable.flag_of_denmark));
        isoCodeToResources.put("NL",
                new LanguageResources("Dutch", R.string.NL, R.drawable.flag_of_the_netherlands));
        isoCodeToResources.put("EN",
                new LanguageResources("English", R.string.EN, R.drawable.flag_of_the_united_kingdom));
        isoCodeToResources.put("EO", new LanguageResources("Esperanto", R.string.EO, R.drawable.flag_of_esperanto));
        isoCodeToResources.put("ET", new LanguageResources("Estonian", R.string.ET, R.drawable.flag_of_estonia));
        isoCodeToResources.put("FI", new LanguageResources("Finnish", R.string.FI, R.drawable.flag_of_finland));
        isoCodeToResources.put("FR", new LanguageResources("French", R.string.FR, R.drawable.flag_of_france));
        isoCodeToResources.put("DE", new LanguageResources("German", R.string.DE, R.drawable.flag_of_germany));
        isoCodeToResources.put("EL", new LanguageResources("Greek", R.string.EL, R.drawable.flag_of_greece));
        isoCodeToResources.put("grc", new LanguageResources("Ancient Greek", R.string.grc));
        isoCodeToResources.put("haw", new LanguageResources("Hawaiian", R.string.haw, R.drawable.flag_of_hawaii));
        isoCodeToResources.put("HE", new LanguageResources("Hebrew", R.string.HE, R.drawable.flag_of_israel));
        isoCodeToResources.put("HI", new LanguageResources("Hindi", R.string.HI, R.drawable.hindi));
        isoCodeToResources.put("HU", new LanguageResources("Hungarian", R.string.HU, R.drawable.flag_of_hungary));
        isoCodeToResources.put("IS", new LanguageResources("Icelandic", R.string.IS, R.drawable.flag_of_iceland));
        isoCodeToResources.put("ID",
                new LanguageResources("Indonesian", R.string.ID, R.drawable.flag_of_indonesia));
        isoCodeToResources.put("GA", new LanguageResources("Irish", R.string.GA, R.drawable.flag_of_ireland));
        isoCodeToResources.put("GD",
                new LanguageResources("Scottish Gaelic", R.string.GD, R.drawable.flag_of_scotland));
        isoCodeToResources.put("GV",
                new LanguageResources("Manx", R.string.GV, R.drawable.flag_of_the_isle_of_man));
        isoCodeToResources.put("IT", new LanguageResources("Italian", R.string.IT, R.drawable.flag_of_italy));
        isoCodeToResources.put("LA", new LanguageResources("Latin", R.string.LA));
        isoCodeToResources.put("LV", new LanguageResources("Latvian", R.string.LV, R.drawable.flag_of_latvia));
        isoCodeToResources.put("LT",
                new LanguageResources("Lithuanian", R.string.LT, R.drawable.flag_of_lithuania));
        isoCodeToResources.put("JA", new LanguageResources("Japanese", R.string.JA, R.drawable.flag_of_japan));
        isoCodeToResources.put("KO", new LanguageResources("Korean", R.string.KO, R.drawable.flag_of_south_korea));
        isoCodeToResources.put("KU", new LanguageResources("Kurdish", R.string.KU));
        isoCodeToResources.put("MS", new LanguageResources("Malay", R.string.MS, R.drawable.flag_of_malaysia));
        isoCodeToResources.put("MI", new LanguageResources("Maori", R.string.MI, R.drawable.flag_of_new_zealand));
        isoCodeToResources.put("MN", new LanguageResources("Mongolian", R.string.MN, R.drawable.flag_of_mongolia));
        isoCodeToResources.put("NE", new LanguageResources("Nepali", R.string.NE, R.drawable.flag_of_nepal));
        isoCodeToResources.put("NO", new LanguageResources("Norwegian", R.string.NO, R.drawable.flag_of_norway));
        isoCodeToResources.put("FA", new LanguageResources("Persian", R.string.FA, R.drawable.flag_of_iran));
        isoCodeToResources.put("PL", new LanguageResources("Polish", R.string.PL, R.drawable.flag_of_poland));
        isoCodeToResources.put("PT", new LanguageResources("Portuguese", R.string.PT, R.drawable.flag_of_portugal));
        isoCodeToResources.put("PA", new LanguageResources("Punjabi", R.string.PA));
        isoCodeToResources.put("RO", new LanguageResources("Romanian", R.string.RO, R.drawable.flag_of_romania));
        isoCodeToResources.put("RU", new LanguageResources("Russian", R.string.RU, R.drawable.flag_of_russia));
        isoCodeToResources.put("SA", new LanguageResources("Sanskrit", R.string.SA));
        isoCodeToResources.put("SR", new LanguageResources("Serbian", R.string.SR, R.drawable.flag_of_serbia));
        isoCodeToResources.put("SK", new LanguageResources("Slovak", R.string.SK, R.drawable.flag_of_slovakia));
        isoCodeToResources.put("SL", new LanguageResources("Slovenian", R.string.SL, R.drawable.flag_of_slovenia));
        isoCodeToResources.put("SO", new LanguageResources("Somali", R.string.SO, R.drawable.flag_of_somalia));
        isoCodeToResources.put("ES", new LanguageResources("Spanish", R.string.ES, R.drawable.flag_of_spain));
        isoCodeToResources.put("SW", new LanguageResources("Swahili", R.string.SW));
        isoCodeToResources.put("SV", new LanguageResources("Swedish", R.string.SV, R.drawable.flag_of_sweden));
        isoCodeToResources.put("TL", new LanguageResources("Tagalog", R.string.TL));
        isoCodeToResources.put("TG", new LanguageResources("Tajik", R.string.TG, R.drawable.flag_of_tajikistan));
        isoCodeToResources.put("TH", new LanguageResources("Thai", R.string.TH, R.drawable.flag_of_thailand));
        isoCodeToResources.put("BO", new LanguageResources("Tibetan", R.string.BO));
        isoCodeToResources.put("TR", new LanguageResources("Turkish", R.string.TR, R.drawable.flag_of_turkey));
        isoCodeToResources.put("UK", new LanguageResources("Ukrainian", R.string.UK, R.drawable.flag_of_ukraine));
        isoCodeToResources.put("UR", new LanguageResources("Urdu", R.string.UR));
        isoCodeToResources.put("VI", new LanguageResources("Vietnamese", R.string.VI, R.drawable.flag_of_vietnam));
        isoCodeToResources.put("CI", new LanguageResources("Welsh", R.string.CI, R.drawable.flag_of_wales_2));
        isoCodeToResources.put("YI", new LanguageResources("Yiddish", R.string.YI));
        isoCodeToResources.put("ZU", new LanguageResources("Zulu", R.string.ZU));
        isoCodeToResources.put("AZ", new LanguageResources("Azeri", R.string.AZ, R.drawable.flag_of_azerbaijan));
        isoCodeToResources.put("EU",
                new LanguageResources("Basque", R.string.EU, R.drawable.flag_of_the_basque_country));
        isoCodeToResources.put("BR", new LanguageResources("Breton", R.string.BR));
        isoCodeToResources.put("MR", new LanguageResources("Marathi", R.string.MR));
        isoCodeToResources.put("FO", new LanguageResources("Faroese", R.string.FO));
        isoCodeToResources.put("GL", new LanguageResources("Galician", R.string.GL, R.drawable.flag_of_galicia));
        isoCodeToResources.put("KA", new LanguageResources("Georgian", R.string.KA, R.drawable.flag_of_georgia));
        isoCodeToResources.put("HT",
                new LanguageResources("Haitian Creole", R.string.HT, R.drawable.flag_of_haiti));
        isoCodeToResources.put("LB",
                new LanguageResources("Luxembourgish", R.string.LB, R.drawable.flag_of_luxembourg));
        isoCodeToResources.put("MK",
                new LanguageResources("Macedonian", R.string.MK, R.drawable.flag_of_macedonia));
        isoCodeToResources.put("LO", new LanguageResources("Lao", R.string.LO, R.drawable.flag_of_laos));
        isoCodeToResources.put("ML", new LanguageResources("Malayalam", R.string.ML));
        isoCodeToResources.put("SL", new LanguageResources("Slovenian", R.string.SL, R.drawable.flag_of_slovenia));
        isoCodeToResources.put("TA", new LanguageResources("Tamil", R.string.TA));
        isoCodeToResources.put("SH", new LanguageResources("Serbo-Croatian", R.string.SH));
        isoCodeToResources.put("SD", new LanguageResources("Sindhi", R.string.SD));

        // Hack to allow lower-case ISO codes to work:
        for (final String isoCode : new ArrayList<String>(isoCodeToResources.keySet())) {
            isoCodeToResources.put(isoCode.toLowerCase(), isoCodeToResources.get(isoCode));
        }

    }

    public static final class DictionaryConfig implements Serializable {
        private static final long serialVersionUID = -1444177164708201263L;
        // User-ordered list, persisted, just the ones that are/have been
        // present.
        final List<String> dictionaryFilesOrdered = new ArrayList<String>();

        final Map<String, DictionaryInfo> uncompressedFilenameToDictionaryInfo = new LinkedHashMap<String, DictionaryInfo>();

        /**
         * Sometimes a deserialized version of this data structure isn't valid.
         * @return
         */
        boolean isValid() {
            return uncompressedFilenameToDictionaryInfo != null && dictionaryFilesOrdered != null;
        }
    }

    DictionaryConfig dictionaryConfig = null;

    int languageButtonPixels = -1;

    static synchronized void staticInit(final Context context) {
        if (DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO != null) {
            return;
        }
        DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO = new LinkedHashMap<String, DictionaryInfo>();
        final BufferedReader reader = new BufferedReader(
                new InputStreamReader(context.getResources().openRawResource(R.raw.dictionary_info)));
        try {
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.startsWith("#") || line.length() == 0) {
                    continue;
                }
                final DictionaryInfo dictionaryInfo = new DictionaryInfo(line);
                DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO.put(dictionaryInfo.uncompressedFilename,
                        dictionaryInfo);
            }
        } catch (IOException e) {
            Log.e(LOG, "Failed to load downloadable dictionary lists.", e);
        }
        try {
            reader.close();
        } catch (IOException e) {
        }
    }

    private File dictDir;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("QuickDic", "Application: onCreate");
        TransliteratorManager.init(null);
        staticInit(getApplicationContext());

        languageButtonPixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60,
                getResources().getDisplayMetrics());

        // Load the dictionaries we know about.
        dictionaryConfig = PersistentObjectCache.init(getApplicationContext()).read(C.DICTIONARY_CONFIGS,
                DictionaryConfig.class);
        if (dictionaryConfig == null) {
            dictionaryConfig = new DictionaryConfig();
        }
        if (!dictionaryConfig.isValid()) {
            dictionaryConfig = new DictionaryConfig();
        }

        // Theme stuff.
        setTheme(getSelectedTheme().themeId);
        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        prefs.registerOnSharedPreferenceChangeListener(new OnSharedPreferenceChangeListener() {
            @Override
            public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
                Log.d("QuickDic", "prefs changed: " + key);
                if (key.equals(getString(R.string.themeKey))) {
                    setTheme(getSelectedTheme().themeId);
                }
            }
        });
    }

    public void onCreateGlobalOptionsMenu(final Context context, final Menu menu) {
        final MenuItem about = menu.add(getString(R.string.about));
        MenuItemCompat.setShowAsAction(about, MenuItem.SHOW_AS_ACTION_NEVER);
        about.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(final MenuItem menuItem) {
                final Intent intent = new Intent(getApplicationContext(), AboutActivity.class);
                context.startActivity(intent);
                return false;
            }
        });

        final MenuItem help = menu.add(getString(R.string.help));
        MenuItemCompat.setShowAsAction(help, MenuItem.SHOW_AS_ACTION_NEVER);
        help.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(final MenuItem menuItem) {
                context.startActivity(HtmlDisplayActivity.getHelpLaunchIntent(getApplicationContext()));
                return false;
            }
        });

        final MenuItem preferences = menu.add(getString(R.string.settings));
        MenuItemCompat.setShowAsAction(preferences, MenuItem.SHOW_AS_ACTION_NEVER);
        preferences.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(final MenuItem menuItem) {
                PreferenceActivity.prefsMightHaveChanged = true;
                final Intent intent = new Intent(getApplicationContext(), PreferenceActivity.class);
                context.startActivity(intent);
                return false;
            }
        });

        final MenuItem reportIssue = menu.add(getString(R.string.reportIssue));
        MenuItemCompat.setShowAsAction(reportIssue, MenuItem.SHOW_AS_ACTION_NEVER);
        reportIssue.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            public boolean onMenuItemClick(final MenuItem menuItem) {
                final Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setData(Uri.parse("http://github.com/rdoeffinger/Dictionary/issues"));
                context.startActivity(intent);
                return false;
            }
        });
    }

    private String selectDefaultDir() {
        final File defaultDictDir = new File(Environment.getExternalStorageDirectory(), "quickDic");
        String dir = defaultDictDir.getAbsolutePath();
        File dictDir = new File(dir);
        if (dictDir.isDirectory() && dictDir.list().length > 0) {
            return dir;
        }
        File efd = null;
        try {
            efd = getApplicationContext().getExternalFilesDir(null);
        } catch (Exception e) {
        }
        if (efd != null) {
            efd.mkdirs();
            if (!dictDir.isDirectory() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                getApplicationContext().getExternalFilesDirs(null);
            }
            if (efd.isDirectory() && efd.canWrite() && checkFileCreate(efd)) {
                return efd.getAbsolutePath();
            }
        }
        if (!dictDir.isDirectory() && !dictDir.mkdirs()) {
            return getApplicationContext().getFilesDir().getAbsolutePath();
        }
        return dir;
    }

    public synchronized File getDictDir() {
        // This metaphor doesn't work, because we've already reset
        // prefsMightHaveChanged.
        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        String dir = prefs.getString(getString(R.string.quickdicDirectoryKey), "");
        if (dir.isEmpty()) {
            dir = selectDefaultDir();
        }
        dictDir = new File(dir);
        dictDir.mkdirs();
        if (!dictDir.isDirectory() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            getApplicationContext().getExternalFilesDirs(null);
        }
        return dictDir;
    }

    static public boolean checkFileCreate(File dir) {
        boolean res = false;
        File testfile = new File(dir, "quickdic_writetest");
        try {
            testfile.delete();
            res = testfile.createNewFile() & testfile.delete();
        } catch (Exception e) {
        }
        return res;
    }

    public File getWordListFile() {
        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        String file = prefs.getString(getString(R.string.wordListFileKey), "");
        if (file.isEmpty()) {
            return new File(getDictDir(), "wordList.txt");
        }
        return new File(file);
    }

    public Theme getSelectedTheme() {
        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        final String theme = prefs.getString(getString(R.string.themeKey), "themeLight");
        if (theme.equals("themeLight")) {
            return Theme.LIGHT;
        } else {
            return Theme.DEFAULT;
        }
    }

    public File getPath(String uncompressedFilename) {
        return new File(getDictDir(), uncompressedFilename);
    }

    String defaultLangISO2 = Locale.getDefault().getLanguage().toLowerCase();
    String defaultLangName = null;
    final Map<String, String> fileToNameCache = new LinkedHashMap<String, String>();

    public String isoCodeToLocalizedLanguageName(final String isoCode) {
        final Language.LanguageResources languageResources = isoCodeToResources.get(isoCode);
        final String lang = languageResources != null ? getApplicationContext().getString(languageResources.nameId)
                : isoCode;
        return lang;
    }

    public List<IndexInfo> sortedIndexInfos(List<IndexInfo> indexInfos) {
        // Hack to put the default locale first in the name.
        if (indexInfos.size() > 1 && indexInfos.get(1).shortName.toLowerCase().equals(defaultLangISO2)) {
            List<IndexInfo> result = new ArrayList<DictionaryInfo.IndexInfo>(indexInfos);
            ListUtil.swap(result, 0, 1);
            return result;
        }
        return indexInfos;
    }

    public synchronized String getDictionaryName(final String uncompressedFilename) {
        final String currentLocale = Locale.getDefault().getLanguage().toLowerCase();
        if (!currentLocale.equals(defaultLangISO2)) {
            defaultLangISO2 = currentLocale;
            fileToNameCache.clear();
            defaultLangName = null;
        }
        if (defaultLangName == null) {
            defaultLangName = isoCodeToLocalizedLanguageName(defaultLangISO2);
        }

        String name = fileToNameCache.get(uncompressedFilename);
        if (name != null) {
            return name;
        }

        final DictionaryInfo dictionaryInfo = DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO
                .get(uncompressedFilename);
        if (dictionaryInfo != null) {
            final StringBuilder nameBuilder = new StringBuilder();

            List<IndexInfo> sortedIndexInfos = sortedIndexInfos(dictionaryInfo.indexInfos);
            for (int i = 0; i < sortedIndexInfos.size(); ++i) {
                if (i > 0) {
                    nameBuilder.append("-");
                }
                nameBuilder.append(isoCodeToLocalizedLanguageName(sortedIndexInfos.get(i).shortName));
            }
            name = nameBuilder.toString();
        } else {
            name = uncompressedFilename.replace(".quickdic", "");
        }
        fileToNameCache.put(uncompressedFilename, name);
        return name;
    }

    public View createButton(final Context context, final DictionaryInfo dictionaryInfo,
            final IndexInfo indexInfo) {
        LanguageResources languageResources = isoCodeToResources.get(indexInfo.shortName);
        View result;

        if (languageResources == null || languageResources.flagId <= 0) {
            Button button = new Button(context);
            button.setText(indexInfo.shortName);
            result = button;
        } else {
            ImageButton button = new ImageButton(context);
            button.setImageResource(languageResources.flagId);
            button.setScaleType(ScaleType.FIT_CENTER);
            result = button;
        }
        result.setLayoutParams(new LinearLayout.LayoutParams(languageButtonPixels, languageButtonPixels * 2 / 3));
        return result;
    }

    public synchronized void moveDictionaryToTop(final DictionaryInfo dictionaryInfo) {
        dictionaryConfig.dictionaryFilesOrdered.remove(dictionaryInfo.uncompressedFilename);
        dictionaryConfig.dictionaryFilesOrdered.add(0, dictionaryInfo.uncompressedFilename);
        PersistentObjectCache.getInstance().write(C.DICTIONARY_CONFIGS, dictionaryConfig);
    }

    public synchronized void deleteDictionary(final DictionaryInfo dictionaryInfo) {
        while (dictionaryConfig.dictionaryFilesOrdered.remove(dictionaryInfo.uncompressedFilename)) {
        }
        dictionaryConfig.uncompressedFilenameToDictionaryInfo.remove(dictionaryInfo.uncompressedFilename);
        getPath(dictionaryInfo.uncompressedFilename).delete();
        PersistentObjectCache.getInstance().write(C.DICTIONARY_CONFIGS, dictionaryConfig);
    }

    final Comparator collator = USE_COLLATOR ? Collator.getInstance() : String.CASE_INSENSITIVE_ORDER;
    final Comparator<String> uncompressedFilenameComparator = new Comparator<String>() {
        @Override
        public int compare(String uncompressedFilename1, String uncompressedFilename2) {
            final String name1 = getDictionaryName(uncompressedFilename1);
            final String name2 = getDictionaryName(uncompressedFilename2);
            if (defaultLangName.length() > 0) {
                if (name1.startsWith(defaultLangName + "-") && !name2.startsWith(defaultLangName + "-")) {
                    return -1;
                } else if (name2.startsWith(defaultLangName + "-") && !name1.startsWith(defaultLangName + "-")) {
                    return 1;
                }
            }
            return collator.compare(name1, name2);
        }
    };
    final Comparator<DictionaryInfo> dictionaryInfoComparator = new Comparator<DictionaryInfo>() {
        @Override
        public int compare(DictionaryInfo d1, DictionaryInfo d2) {
            // Single-index dictionaries first.
            if (d1.indexInfos.size() != d2.indexInfos.size()) {
                return d1.indexInfos.size() - d2.indexInfos.size();
            }
            return uncompressedFilenameComparator.compare(d1.uncompressedFilename, d2.uncompressedFilename);
        }
    };

    public void backgroundUpdateDictionaries(final Runnable onUpdateFinished) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                final DictionaryConfig oldDictionaryConfig = new DictionaryConfig();
                synchronized (DictionaryApplication.this) {
                    oldDictionaryConfig.dictionaryFilesOrdered.addAll(dictionaryConfig.dictionaryFilesOrdered);
                }
                final DictionaryConfig newDictionaryConfig = new DictionaryConfig();
                for (final String uncompressedFilename : oldDictionaryConfig.dictionaryFilesOrdered) {
                    final File dictFile = getPath(uncompressedFilename);
                    final DictionaryInfo dictionaryInfo = Dictionary.getDictionaryInfo(dictFile);
                    if (dictionaryInfo.isValid() || dictFile.exists()) {
                        newDictionaryConfig.dictionaryFilesOrdered.add(uncompressedFilename);
                        newDictionaryConfig.uncompressedFilenameToDictionaryInfo.put(uncompressedFilename,
                                dictionaryInfo);
                    }
                }

                // Are there dictionaries on the device that we didn't know
                // about already?
                // Pick them up and put them at the end of the list.
                final List<String> toAddSorted = new ArrayList<String>();
                final File[] dictDirFiles = getDictDir().listFiles();
                if (dictDirFiles != null) {
                    for (final File file : dictDirFiles) {
                        if (file.getName().endsWith(".zip")) {
                            if (DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO
                                    .containsKey(file.getName().replace(".zip", ""))) {
                                file.delete();
                            }
                        }
                        if (!file.getName().endsWith(".quickdic")) {
                            continue;
                        }
                        if (newDictionaryConfig.uncompressedFilenameToDictionaryInfo.containsKey(file.getName())) {
                            // We have it in our list already.
                            continue;
                        }
                        final DictionaryInfo dictionaryInfo = Dictionary.getDictionaryInfo(file);
                        if (!dictionaryInfo.isValid()) {
                            Log.e(LOG, "Unable to parse dictionary: " + file.getPath());
                        }

                        toAddSorted.add(file.getName());
                        newDictionaryConfig.uncompressedFilenameToDictionaryInfo.put(file.getName(),
                                dictionaryInfo);
                    }
                } else {
                    Log.w(LOG, "dictDir is not a directory: " + getDictDir().getPath());
                }
                if (!toAddSorted.isEmpty()) {
                    Collections.sort(toAddSorted, uncompressedFilenameComparator);
                    newDictionaryConfig.dictionaryFilesOrdered.addAll(toAddSorted);
                }

                try {
                    PersistentObjectCache.getInstance().write(C.DICTIONARY_CONFIGS, newDictionaryConfig);
                } catch (Exception e) {
                    Log.e(LOG, "Failed persisting dictionary configs", e);
                }

                synchronized (DictionaryApplication.this) {
                    dictionaryConfig = newDictionaryConfig;
                }

                try {
                    onUpdateFinished.run();
                } catch (Exception e) {
                    Log.e(LOG, "Exception running callback.", e);
                }
            }
        }).start();
    }

    public boolean matchesFilters(final DictionaryInfo dictionaryInfo, final String[] filters) {
        if (filters == null) {
            return true;
        }
        for (final String filter : filters) {
            if (!getDictionaryName(dictionaryInfo.uncompressedFilename).toLowerCase().contains(filter)) {
                return false;
            }
        }
        return true;
    }

    public synchronized List<DictionaryInfo> getDictionariesOnDevice(String[] filters) {
        final List<DictionaryInfo> result = new ArrayList<DictionaryInfo>(
                dictionaryConfig.dictionaryFilesOrdered.size());
        for (final String uncompressedFilename : dictionaryConfig.dictionaryFilesOrdered) {
            final DictionaryInfo dictionaryInfo = dictionaryConfig.uncompressedFilenameToDictionaryInfo
                    .get(uncompressedFilename);
            if (dictionaryInfo != null && matchesFilters(dictionaryInfo, filters)) {
                result.add(dictionaryInfo);
            }
        }
        return result;
    }

    public List<DictionaryInfo> getDownloadableDictionaries(String[] filters) {
        final List<DictionaryInfo> result = new ArrayList<DictionaryInfo>(
                dictionaryConfig.dictionaryFilesOrdered.size());

        final Map<String, DictionaryInfo> remaining = new LinkedHashMap<String, DictionaryInfo>(
                DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO);
        remaining.keySet().removeAll(dictionaryConfig.dictionaryFilesOrdered);
        for (final DictionaryInfo dictionaryInfo : remaining.values()) {
            if (matchesFilters(dictionaryInfo, filters)) {
                result.add(dictionaryInfo);
            }
        }
        Collections.sort(result, dictionaryInfoComparator);
        return result;
    }

    public synchronized boolean isDictionaryOnDevice(String uncompressedFilename) {
        return dictionaryConfig.uncompressedFilenameToDictionaryInfo.get(uncompressedFilename) != null;
    }

    public boolean updateAvailable(final DictionaryInfo dictionaryInfo) {
        final DictionaryInfo downloadable = DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO
                .get(dictionaryInfo.uncompressedFilename);
        return downloadable != null && downloadable.creationMillis > dictionaryInfo.creationMillis;
    }

    public DictionaryInfo getDownloadable(final String uncompressedFilename) {
        final DictionaryInfo downloadable = DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO
                .get(uncompressedFilename);
        return downloadable;
    }

}