]> gitweb.fperrin.net Git - Dictionary.git/blob - src/com/hughes/android/dictionary/DictionaryApplication.java
Fixed add to wordlist on fresh install.
[Dictionary.git] / src / com / hughes / android / dictionary / DictionaryApplication.java
1 // Copyright 2011 Google Inc. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package com.hughes.android.dictionary;
16
17 import android.app.Application;
18 import android.content.Context;
19 import android.content.Intent;
20 import android.content.SharedPreferences;
21 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
22 import android.net.Uri;
23 import android.os.Environment;
24 import android.preference.PreferenceManager;
25 import android.util.Log;
26 import android.util.TypedValue;
27 import android.view.View;
28 import android.widget.Button;
29 import android.widget.ImageButton;
30 import android.widget.ImageView.ScaleType;
31
32 import com.actionbarsherlock.view.Menu;
33 import com.actionbarsherlock.view.MenuItem;
34 import com.actionbarsherlock.view.MenuItem.OnMenuItemClickListener;
35 import com.hughes.android.dictionary.DictionaryInfo.IndexInfo;
36 import com.hughes.android.dictionary.engine.Dictionary;
37 import com.hughes.android.dictionary.engine.Language;
38 import com.hughes.android.dictionary.engine.Language.LanguageResources;
39 import com.hughes.android.dictionary.engine.TransliteratorManager;
40 import com.hughes.android.util.PersistentObjectCache;
41 import com.hughes.util.ListUtil;
42 import com.ibm.icu.text.Collator;
43
44 import java.io.BufferedReader;
45 import java.io.File;
46 import java.io.IOException;
47 import java.io.InputStreamReader;
48 import java.io.Serializable;
49 import java.util.ArrayList;
50 import java.util.Collections;
51 import java.util.Comparator;
52 import java.util.LinkedHashMap;
53 import java.util.List;
54 import java.util.Locale;
55 import java.util.Map;
56
57 public class DictionaryApplication extends Application {
58
59     static final String LOG = "QuickDicApp";
60
61     // Static, determined by resources (and locale).
62     // Unordered.
63     static Map<String, DictionaryInfo> DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO = null;
64
65     static final class DictionaryConfig implements Serializable {
66         private static final long serialVersionUID = -1444177164708201263L;
67         // User-ordered list, persisted, just the ones that are/have been
68         // present.
69         final List<String> dictionaryFilesOrdered = new ArrayList<String>();
70
71         final Map<String, DictionaryInfo> uncompressedFilenameToDictionaryInfo = new LinkedHashMap<String, DictionaryInfo>();
72         
73         /**
74          * Sometimes a deserialized version of this data structure isn't valid.
75          * @return
76          */
77         boolean isValid() {
78             return uncompressedFilenameToDictionaryInfo != null && dictionaryFilesOrdered != null;
79         }
80     }
81
82     DictionaryConfig dictionaryConfig = null;
83
84     int languageButtonPixels = -1;
85
86     static synchronized void staticInit(final Context context) {
87         if (DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO != null) {
88             return;
89         }
90         DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO = new LinkedHashMap<String, DictionaryInfo>();
91         final BufferedReader reader = new BufferedReader(
92                 new InputStreamReader(context.getResources().openRawResource(R.raw.dictionary_info)));
93         try {
94             String line;
95             while ((line = reader.readLine()) != null) {
96                 if (line.startsWith("#") || line.length() == 0) {
97                     continue;
98                 }
99                 final DictionaryInfo dictionaryInfo = new DictionaryInfo(line);
100                 DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO.put(
101                         dictionaryInfo.uncompressedFilename, dictionaryInfo);
102             }
103             reader.close();
104         } catch (IOException e) {
105             Log.e(LOG, "Failed to load downloadable dictionary lists.", e);
106         }
107     }
108
109     private File dictDir;
110
111     @Override
112     public void onCreate() {
113         super.onCreate();
114         Log.d("QuickDic", "Application: onCreate");
115         TransliteratorManager.init(null);
116         staticInit(getApplicationContext());
117
118         languageButtonPixels = (int) TypedValue.applyDimension(
119                 TypedValue.COMPLEX_UNIT_DIP, 60, getResources().getDisplayMetrics());
120
121         // Load the dictionaries we know about.
122         dictionaryConfig = PersistentObjectCache.init(getApplicationContext()).read(
123                 C.DICTIONARY_CONFIGS, DictionaryConfig.class);
124         if (dictionaryConfig == null) {
125             dictionaryConfig = new DictionaryConfig();
126         }
127         if (!dictionaryConfig.isValid()) {
128             dictionaryConfig = new DictionaryConfig();
129         }
130
131         // Theme stuff.
132         setTheme(getSelectedTheme().themeId);
133         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
134         prefs.registerOnSharedPreferenceChangeListener(new OnSharedPreferenceChangeListener() {
135             @Override
136             public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
137                     String key) {
138                 Log.d("QuickDic", "prefs changed: " + key);
139                 if (key.equals(getString(R.string.themeKey))) {
140                     setTheme(getSelectedTheme().themeId);
141                 }
142             }
143         });
144     }
145
146     public void onCreateGlobalOptionsMenu(
147             final Context context, final Menu menu) {
148         final MenuItem about = menu.add(getString(R.string.about));
149         about.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
150         about.setOnMenuItemClickListener(new OnMenuItemClickListener() {
151             public boolean onMenuItemClick(final MenuItem menuItem) {
152                 final Intent intent = new Intent().setClassName(AboutActivity.class
153                         .getPackage().getName(), AboutActivity.class.getCanonicalName());
154                 context.startActivity(intent);
155                 return false;
156             }
157         });
158
159         final MenuItem help = menu.add(getString(R.string.help));
160         help.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
161         help.setOnMenuItemClickListener(new OnMenuItemClickListener() {
162             public boolean onMenuItemClick(final MenuItem menuItem) {
163                 context.startActivity(HtmlDisplayActivity.getHelpLaunchIntent());
164                 return false;
165             }
166         });
167
168         final MenuItem preferences = menu.add(getString(R.string.settings));
169         preferences.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
170         preferences.setOnMenuItemClickListener(new OnMenuItemClickListener() {
171             public boolean onMenuItemClick(final MenuItem menuItem) {
172                 PreferenceActivity.prefsMightHaveChanged = true;
173                 final Intent intent = new Intent().setClassName(PreferenceActivity.class
174                         .getPackage().getName(), PreferenceActivity.class.getCanonicalName());
175                 context.startActivity(intent);
176                 return false;
177             }
178         });
179
180         final MenuItem reportIssue = menu.add(getString(R.string.reportIssue));
181         reportIssue.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
182         reportIssue.setOnMenuItemClickListener(new OnMenuItemClickListener() {
183             public boolean onMenuItemClick(final MenuItem menuItem) {
184                 final Intent intent = new Intent(Intent.ACTION_VIEW);
185                 intent.setData(Uri
186                         .parse("http://code.google.com/p/quickdic-dictionary/issues/entry"));
187                 context.startActivity(intent);
188                 return false;
189             }
190         });
191     }
192
193     public synchronized File getDictDir() {
194         // This metaphor doesn't work, because we've already reset
195         // prefsMightHaveChanged.
196         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
197         String dir = prefs.getString(getString(R.string.quickdicDirectoryKey), "");
198         if (dir.isEmpty()) {
199             final File defaultDictDir = new File(Environment.getExternalStorageDirectory(), "quickDic");
200             dir = defaultDictDir.getAbsolutePath();
201         }
202         dictDir = new File(dir);
203         dictDir.mkdirs();
204         return dictDir;
205     }
206
207     public File getWordListFile() {
208         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
209         String file = prefs.getString(getString(R.string.wordListFileKey), "");
210         if (file.isEmpty()) {
211             return new File(getDictDir(), "wordList.txt");
212         }
213         return new File(file);
214     }
215
216     public C.Theme getSelectedTheme() {
217         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
218         final String theme = prefs.getString(getString(R.string.themeKey), "themeLight");
219         if (theme.equals("themeLight")) {
220             return C.Theme.LIGHT;
221         } else {
222             return C.Theme.DEFAULT;
223         }
224     }
225
226     public File getPath(String uncompressedFilename) {
227         return new File(getDictDir(), uncompressedFilename);
228     }
229
230     String defaultLangISO2 = Locale.getDefault().getLanguage().toLowerCase();
231     String defaultLangName = null;
232     final Map<String, String> fileToNameCache = new LinkedHashMap<String, String>();
233
234     public String isoCodeToLocalizedLanguageName(final String isoCode) {
235         final Language.LanguageResources languageResources = Language.isoCodeToResources
236                 .get(isoCode);
237         final String lang = languageResources != null ? getApplicationContext().getString(
238                 languageResources.nameId) : isoCode;
239         return lang;
240     }
241
242     public List<IndexInfo> sortedIndexInfos(List<IndexInfo> indexInfos) {
243         // Hack to put the default locale first in the name.
244         if (indexInfos.size() > 1 &&
245                 indexInfos.get(1).shortName.toLowerCase().equals(defaultLangISO2)) {
246             List<IndexInfo> result = new ArrayList<DictionaryInfo.IndexInfo>(indexInfos);
247             ListUtil.swap(result, 0, 1);
248             return result;
249         }
250         return indexInfos;
251     }
252
253     public synchronized String getDictionaryName(final String uncompressedFilename) {
254         final String currentLocale = Locale.getDefault().getLanguage().toLowerCase();
255         if (!currentLocale.equals(defaultLangISO2)) {
256             defaultLangISO2 = currentLocale;
257             fileToNameCache.clear();
258             defaultLangName = null;
259         }
260         if (defaultLangName == null) {
261             defaultLangName = isoCodeToLocalizedLanguageName(defaultLangISO2);
262         }
263
264         String name = fileToNameCache.get(uncompressedFilename);
265         if (name != null) {
266             return name;
267         }
268
269         final DictionaryInfo dictionaryInfo = DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO
270                 .get(uncompressedFilename);
271         if (dictionaryInfo != null) {
272             final StringBuilder nameBuilder = new StringBuilder();
273
274             List<IndexInfo> sortedIndexInfos = sortedIndexInfos(dictionaryInfo.indexInfos);
275             for (int i = 0; i < sortedIndexInfos.size(); ++i) {
276                 if (i > 0) {
277                     nameBuilder.append("-");
278                 }
279                 nameBuilder
280                         .append(isoCodeToLocalizedLanguageName(sortedIndexInfos.get(i).shortName));
281             }
282             name = nameBuilder.toString();
283         } else {
284             name = uncompressedFilename.replace(".quickdic", "");
285         }
286         fileToNameCache.put(uncompressedFilename, name);
287         return name;
288     }
289
290     public View createButton(final Context context, final DictionaryInfo dictionaryInfo,
291             final IndexInfo indexInfo) {
292         LanguageResources languageResources = Language.isoCodeToResources.get(indexInfo.shortName);
293         View result;
294
295         if (languageResources == null || languageResources.flagId <= 0) {
296             Button button = new Button(context);
297             button.setText(indexInfo.shortName);
298             result = button;
299         } else {
300             ImageButton button = new ImageButton(context);
301             button.setImageResource(languageResources.flagId);
302             button.setScaleType(ScaleType.FIT_CENTER);
303             result = button;
304         }
305         result.setMinimumWidth(languageButtonPixels);
306         result.setMinimumHeight(languageButtonPixels * 2 / 3);
307         // result.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
308         // LayoutParams.WRAP_CONTENT));
309         return result;
310     }
311
312     public synchronized void moveDictionaryToTop(final DictionaryInfo dictionaryInfo) {
313         dictionaryConfig.dictionaryFilesOrdered.remove(dictionaryInfo.uncompressedFilename);
314         dictionaryConfig.dictionaryFilesOrdered.add(0, dictionaryInfo.uncompressedFilename);
315         PersistentObjectCache.getInstance().write(C.DICTIONARY_CONFIGS, dictionaryConfig);
316     }
317
318     public synchronized void deleteDictionary(final DictionaryInfo dictionaryInfo) {
319         while (dictionaryConfig.dictionaryFilesOrdered.remove(dictionaryInfo.uncompressedFilename)) {
320         }
321         ;
322         dictionaryConfig.uncompressedFilenameToDictionaryInfo
323                 .remove(dictionaryInfo.uncompressedFilename);
324         getPath(dictionaryInfo.uncompressedFilename).delete();
325         PersistentObjectCache.getInstance().write(C.DICTIONARY_CONFIGS, dictionaryConfig);
326     }
327
328     final Collator collator = Collator.getInstance();
329     final Comparator<String> uncompressedFilenameComparator = new Comparator<String>() {
330         @Override
331         public int compare(String uncompressedFilename1, String uncompressedFilename2) {
332             final String name1 = getDictionaryName(uncompressedFilename1);
333             final String name2 = getDictionaryName(uncompressedFilename2);
334             if (defaultLangName.length() > 0) {
335                 if (name1.startsWith(defaultLangName + "-")
336                         && !name2.startsWith(defaultLangName + "-")) {
337                     return -1;
338                 } else if (name2.startsWith(defaultLangName + "-")
339                         && !name1.startsWith(defaultLangName + "-")) {
340                     return 1;
341                 }
342             }
343             return collator.compare(name1, name2);
344         }
345     };
346     final Comparator<DictionaryInfo> dictionaryInfoComparator = new Comparator<DictionaryInfo>() {
347         @Override
348         public int compare(DictionaryInfo d1, DictionaryInfo d2) {
349             // Single-index dictionaries first.
350             if (d1.indexInfos.size() != d2.indexInfos.size()) {
351                 return d1.indexInfos.size() - d2.indexInfos.size();
352             }
353             return uncompressedFilenameComparator.compare(d1.uncompressedFilename,
354                     d2.uncompressedFilename);
355         }
356     };
357
358     public void backgroundUpdateDictionaries(final Runnable onUpdateFinished) {
359         new Thread(new Runnable() {
360             @Override
361             public void run() {
362                 final DictionaryConfig oldDictionaryConfig = new DictionaryConfig();
363                 synchronized (this) {
364                     oldDictionaryConfig.dictionaryFilesOrdered
365                             .addAll(dictionaryConfig.dictionaryFilesOrdered);
366                 }
367                 final DictionaryConfig newDictionaryConfig = new DictionaryConfig();
368                 for (final String uncompressedFilename : oldDictionaryConfig.dictionaryFilesOrdered) {
369                     final File dictFile = getPath(uncompressedFilename);
370                     final DictionaryInfo dictionaryInfo = Dictionary.getDictionaryInfo(dictFile);
371                     if (dictionaryInfo != null) {
372                         newDictionaryConfig.dictionaryFilesOrdered.add(uncompressedFilename);
373                         newDictionaryConfig.uncompressedFilenameToDictionaryInfo.put(
374                                 uncompressedFilename, dictionaryInfo);
375                     }
376                 }
377
378                 // Are there dictionaries on the device that we didn't know
379                 // about already?
380                 // Pick them up and put them at the end of the list.
381                 final List<String> toAddSorted = new ArrayList<String>();
382                 final File[] dictDirFiles = getDictDir().listFiles();
383                 if (dictDirFiles != null) {
384                     for (final File file : dictDirFiles) {
385                         if (file.getName().endsWith(".zip")) {
386                             if (DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO
387                                     .containsKey(file.getName().replace(".zip", ""))) {
388                                 file.delete();
389                             }
390                         }
391                         if (!file.getName().endsWith(".quickdic")) {
392                             continue;
393                         }
394                         if (newDictionaryConfig.uncompressedFilenameToDictionaryInfo
395                                 .containsKey(file.getName())) {
396                             // We have it in our list already.
397                             continue;
398                         }
399                         final DictionaryInfo dictionaryInfo = Dictionary.getDictionaryInfo(file);
400                         if (dictionaryInfo == null) {
401                             Log.e(LOG, "Unable to parse dictionary: " + file.getPath());
402                             continue;
403                         }
404
405                         toAddSorted.add(file.getName());
406                         newDictionaryConfig.uncompressedFilenameToDictionaryInfo.put(
407                                 file.getName(), dictionaryInfo);
408                     }
409                 } else {
410                     Log.w(LOG, "dictDir is not a diretory: " + getDictDir().getPath());
411                 }
412                 if (!toAddSorted.isEmpty()) {
413                     Collections.sort(toAddSorted, uncompressedFilenameComparator);
414                     newDictionaryConfig.dictionaryFilesOrdered.addAll(toAddSorted);
415                 }
416
417                 PersistentObjectCache.getInstance()
418                         .write(C.DICTIONARY_CONFIGS, newDictionaryConfig);
419                 synchronized (this) {
420                     dictionaryConfig = newDictionaryConfig;
421                 }
422
423                 try {
424                     onUpdateFinished.run();
425                 } catch (Exception e) {
426                     Log.e(LOG, "Exception running callback.", e);
427                 }
428             }
429         }).start();
430     }
431
432     public boolean matchesFilters(final DictionaryInfo dictionaryInfo, final String[] filters) {
433         if (filters == null) {
434             return true;
435         }
436         for (final String filter : filters) {
437             if (!getDictionaryName(dictionaryInfo.uncompressedFilename).toLowerCase().contains(
438                     filter)) {
439                 return false;
440             }
441         }
442         return true;
443     }
444
445     public synchronized List<DictionaryInfo> getDictionariesOnDevice(String[] filters) {
446         final List<DictionaryInfo> result = new ArrayList<DictionaryInfo>(
447                 dictionaryConfig.dictionaryFilesOrdered.size());
448         for (final String uncompressedFilename : dictionaryConfig.dictionaryFilesOrdered) {
449             final DictionaryInfo dictionaryInfo = dictionaryConfig.uncompressedFilenameToDictionaryInfo
450                     .get(uncompressedFilename);
451             if (dictionaryInfo != null && matchesFilters(dictionaryInfo, filters)) {
452                 result.add(dictionaryInfo);
453             }
454         }
455         return result;
456     }
457
458     public List<DictionaryInfo> getDownloadableDictionaries(String[] filters) {
459         final List<DictionaryInfo> result = new ArrayList<DictionaryInfo>(
460                 dictionaryConfig.dictionaryFilesOrdered.size());
461
462         final Map<String, DictionaryInfo> remaining = new LinkedHashMap<String, DictionaryInfo>(
463                 DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO);
464         remaining.keySet().removeAll(dictionaryConfig.dictionaryFilesOrdered);
465         for (final DictionaryInfo dictionaryInfo : remaining.values()) {
466             if (matchesFilters(dictionaryInfo, filters)) {
467                 result.add(dictionaryInfo);
468             }
469         }
470         Collections.sort(result, dictionaryInfoComparator);
471         return result;
472     }
473
474     public synchronized boolean isDictionaryOnDevice(String uncompressedFilename) {
475         return dictionaryConfig.uncompressedFilenameToDictionaryInfo.get(uncompressedFilename) != null;
476     }
477
478     public boolean updateAvailable(final DictionaryInfo dictionaryInfo) {
479         final DictionaryInfo downloadable =
480                 DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO.get(
481                         dictionaryInfo.uncompressedFilename);
482         return downloadable != null &&
483                 downloadable.creationMillis > dictionaryInfo.creationMillis;
484     }
485
486     public DictionaryInfo getDownloadable(final String uncompressedFilename) {
487         final DictionaryInfo downloadable = DOWNLOADABLE_UNCOMPRESSED_FILENAME_NAME_TO_DICTIONARY_INFO
488                 .get(uncompressedFilename);
489         return downloadable;
490     }
491
492 }