]> gitweb.fperrin.net Git - Dictionary.git/blob - src/com/hughes/android/dictionary/DictionaryActivity.java
go
[Dictionary.git] / src / com / hughes / android / dictionary / DictionaryActivity.java
1 package com.hughes.android.dictionary;
2
3 import java.io.File;
4 import java.io.FileWriter;
5 import java.io.IOException;
6 import java.io.PrintWriter;
7 import java.io.RandomAccessFile;
8 import java.text.SimpleDateFormat;
9 import java.util.Arrays;
10 import java.util.Date;
11 import java.util.concurrent.Executor;
12 import java.util.concurrent.Executors;
13 import java.util.concurrent.atomic.AtomicBoolean;
14
15 import android.app.ListActivity;
16 import android.content.Context;
17 import android.content.Intent;
18 import android.content.SharedPreferences;
19 import android.content.SharedPreferences.Editor;
20 import android.graphics.Typeface;
21 import android.os.Bundle;
22 import android.os.Handler;
23 import android.preference.PreferenceManager;
24 import android.text.ClipboardManager;
25 import android.text.Editable;
26 import android.text.Spannable;
27 import android.text.TextWatcher;
28 import android.text.style.StyleSpan;
29 import android.util.Log;
30 import android.view.ContextMenu;
31 import android.view.KeyEvent;
32 import android.view.Menu;
33 import android.view.MenuItem;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.view.ContextMenu.ContextMenuInfo;
37 import android.view.MenuItem.OnMenuItemClickListener;
38 import android.view.View.OnClickListener;
39 import android.view.inputmethod.InputMethodManager;
40 import android.widget.AdapterView;
41 import android.widget.BaseAdapter;
42 import android.widget.Button;
43 import android.widget.EditText;
44 import android.widget.ListView;
45 import android.widget.TableLayout;
46 import android.widget.TableRow;
47 import android.widget.TextView;
48 import android.widget.Toast;
49
50 import com.hughes.android.dictionary.Dictionary.IndexEntry;
51 import com.hughes.android.dictionary.Dictionary.LanguageData;
52 import com.hughes.android.dictionary.Dictionary.Row;
53 import com.hughes.android.dictionary.engine.Language;
54 import com.ibm.icu.text.Collator;
55
56 public class DictionaryActivity extends ListActivity {
57   
58   // TO DO:
59   // * Easy reverse lookup.
60   // * Download latest dicts.
61   //   * http://ftp.tu-chemnitz.de/pub/Local/urz/ding/de-en-devel/
62   //   * http://www1.dict.cc/translation_file_request.php?l=e
63   // * Compress all the strings everywhere, put compression table in file.
64   // Done:
65   // * Only one way to way for current search to end. (won't do).
66
67   static final String LOG = "QuickDic";
68   static final String PREF_DICT_ACTIVE_LANG = "DICT_DIR_PREF";
69   static final String PREF_ACTIVE_SEARCH_TEXT = "ACTIVE_WORD_PREF";
70
71   // package for test.
72   final Handler uiHandler = new Handler();
73   private final Executor searchExecutor = Executors.newSingleThreadExecutor();
74
75   EditText searchText;
76   Button langButton;
77   int lastSelectedRow = 0;  // TODO: I'm evil.
78
79   private boolean prefsMightHaveChanged = true;
80
81   // Never null.
82   private File wordList;
83   private RandomAccessFile dictRaf = null;
84   private Dictionary dictionary = null;
85   private boolean saveOnlyFirstSubentry = false;
86
87   // Visible for testing.
88   LanguageListAdapter languageList = null;
89   private SearchOperation searchOperation = null;
90   
91   public DictionaryActivity() {
92
93     searchExecutor.execute(new Runnable() {
94       public void run() {
95         final long startMillis = System.currentTimeMillis();
96         for (final String lang : Arrays.asList("EN", "DE")) {
97           Language.lookup(lang).getFindCollator(); 
98           final Collator c = Language.lookup(lang).getSortCollator(); 
99           if (c.compare("pre-print", "preppy") >= 0) {
100             Log.e(LOG, c.getClass() + " is buggy, lookups may not work properly.");
101           }
102         }
103         Log.d(LOG, "Loading collators took:" + (System.currentTimeMillis() - startMillis));
104       }
105     });
106
107   }
108
109   /** Called when the activity is first created. */
110   @Override
111   public void onCreate(Bundle savedInstanceState) {
112     super.onCreate(savedInstanceState);
113     Log.d(LOG, "onCreate:" + this);
114
115     try {
116       initDictionaryAndPrefs();
117     } catch (Exception e) {
118       return;
119     }
120
121     // UI init.
122
123     setContentView(R.layout.dictionary_activity);
124     searchText = (EditText) findViewById(R.id.SearchText);
125     langButton = (Button) findViewById(R.id.LangButton);
126     
127     Log.d(LOG, "adding text changed listener");
128     searchText.addTextChangedListener(new SearchTextWatcher());
129     
130     getListView().setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
131       public void onItemSelected(AdapterView<?> arg0, View arg1, int row,
132           long arg3) {
133         setSelectedRow(row);
134       }
135       public void onNothingSelected(AdapterView<?> arg0) {
136       }
137     });
138     
139     getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
140       public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int row,
141           long arg3) {
142         setSelectedRow(row);
143         return false;
144       }
145     });
146     
147     final Button clearSearchTextButton = (Button) findViewById(R.id.ClearSearchTextButton);
148     clearSearchTextButton.setOnClickListener(new OnClickListener() {
149       public void onClick(View v) {
150         onClearSearchTextButton(clearSearchTextButton);
151       }
152     });
153     clearSearchTextButton.setVisibility(PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
154         getString(R.string.showClearSearchTextButtonKey), true) ? View.VISIBLE
155         : View.GONE);
156     
157     final Button langButton = (Button) findViewById(R.id.LangButton);
158     langButton.setOnClickListener(new OnClickListener() {
159       public void onClick(View v) {
160         onLanguageButton();
161       }
162     });
163     
164     final Button upButton = (Button) findViewById(R.id.UpButton);
165     upButton.setOnClickListener(new OnClickListener() {
166       public void onClick(View v) {
167         onUpButton();
168       }
169     });
170     final Button downButton = (Button) findViewById(R.id.DownButton);
171     downButton.setOnClickListener(new OnClickListener() {
172       public void onClick(View v) {
173         onDownButton();
174       }
175     });
176
177     // ContextMenu.
178     registerForContextMenu(getListView());
179
180     updateLangButton();
181   }
182   
183   private void initDictionaryAndPrefs() throws Exception {
184     if (!prefsMightHaveChanged) {
185       return;
186     }
187     closeCurrentDictionary();
188     
189     final SharedPreferences prefs = PreferenceManager
190         .getDefaultSharedPreferences(this);
191     wordList = new File(prefs.getString(getString(R.string.wordListFileKey),
192         getString(R.string.wordListFileDefault)));
193     Log.d(LOG, "wordList=" + wordList);
194     
195     saveOnlyFirstSubentry = prefs.getBoolean(getString(R.string.saveOnlyFirstSubentryKey), false);
196
197     final File dictFile = new File(prefs.getString(getString(R.string.dictFileKey),
198         getString(R.string.dictFileDefault)));
199     Log.d(LOG, "dictFile=" + dictFile);
200     
201     try {
202       if (!dictFile.canRead()) {
203         throw new IOException("Unable to read dictionary file.");
204       }
205       
206       dictRaf = new RandomAccessFile(dictFile, "r");
207       final long startMillis = System.currentTimeMillis();
208       dictionary = new Dictionary(dictRaf);
209       Log.d(LOG, "Read dictionary millis: " + (System.currentTimeMillis() - startMillis));
210     } catch (IOException e) {
211       Log.e(LOG, "Couldn't open dictionary.", e);
212       this.startActivity(new Intent(this, NoDictionaryActivity.class));
213       finish();
214       throw new Exception(e);
215     }
216     
217     final byte lang = prefs.getInt(PREF_DICT_ACTIVE_LANG, SimpleEntry.LANG1) == SimpleEntry.LANG1 ? SimpleEntry.LANG1
218         : SimpleEntry.LANG2;
219     
220     languageList = new LanguageListAdapter(dictionary.languageDatas[lang]);
221     setListAdapter(languageList);
222     prefsMightHaveChanged = false;
223   }
224
225   @Override
226   public void onResume() {
227     super.onResume();
228     Log.d(LOG, "onResume:" + this);
229
230     try {
231       initDictionaryAndPrefs();
232     } catch (Exception e) {
233       return;
234     }
235     
236     final SharedPreferences prefs = PreferenceManager
237         .getDefaultSharedPreferences(this);
238     final String searchTextString = prefs
239         .getString(PREF_ACTIVE_SEARCH_TEXT, "");
240     searchText.setText(searchTextString);
241     getListView().requestFocus();
242     onSearchTextChange(searchTextString);
243   }
244
245   @Override
246   public void onPause() {
247     super.onPause();
248     Log.d(LOG, "onPause:" + this);
249     final Editor prefs = PreferenceManager.getDefaultSharedPreferences(this)
250         .edit();
251     prefs.putInt(PREF_DICT_ACTIVE_LANG, languageList.languageData.lang);
252     prefs.putString(PREF_ACTIVE_SEARCH_TEXT, searchText.getText().toString());
253     prefs.commit();
254   }
255
256   @Override
257   public void onStop() {
258     super.onStop();
259     Log.d(LOG, "onStop:" + this);
260     if (isFinishing()) {
261       Log.i(LOG, "isFinishing()==true, closing dictionary.");
262       closeCurrentDictionary();
263     }
264   }
265
266   private void closeCurrentDictionary() {
267     Log.i(LOG, "closeCurrentDictionary");
268     if (dictionary == null) {
269       return;
270     }
271     waitForSearchEnd();
272     languageList = null;
273     setListAdapter(null);
274     Log.d(LOG, "setListAdapter finished.");
275     dictionary = null;
276     try {
277       if (dictRaf != null) {
278         dictRaf.close();
279       }
280     } catch (IOException e) {
281       throw new RuntimeException(e);
282     }
283     dictRaf = null;
284   }
285
286   public String getSelectedRowRawText(final boolean onlyFirstSubentry) {
287     final Row row = languageList.languageData.rows.get(getSelectedRow());
288     return languageList.languageData.rowToString(row, onlyFirstSubentry);
289   }
290
291   // ----------------------------------------------------------------
292   // OptionsMenu
293   // ----------------------------------------------------------------
294
295   private MenuItem switchLanguageMenuItem = null;
296
297   @Override
298   public boolean onCreateOptionsMenu(final Menu menu) {
299     switchLanguageMenuItem = menu.add(getString(R.string.switchToLanguage));
300     switchLanguageMenuItem
301         .setOnMenuItemClickListener(new OnMenuItemClickListener() {
302           public boolean onMenuItemClick(final MenuItem menuItem) {
303             onLanguageButton();
304             return false;
305           }
306         });
307
308     final MenuItem preferences = menu.add(getString(R.string.preferences));
309     preferences.setOnMenuItemClickListener(new OnMenuItemClickListener() {
310       public boolean onMenuItemClick(final MenuItem menuItem) {
311         prefsMightHaveChanged = true;
312         startActivity(new Intent(DictionaryActivity.this,
313             PreferenceActivity.class));
314         return false;
315       }
316     });
317
318     final MenuItem about = menu.add(getString(R.string.about));
319     about.setOnMenuItemClickListener(new OnMenuItemClickListener() {
320       public boolean onMenuItemClick(final MenuItem menuItem) {
321         final Intent intent = new Intent().setClassName(AboutActivity.class
322             .getPackage().getName(), AboutActivity.class.getCanonicalName());
323         final String currentDictInfo;
324         if (dictionary == null) {
325           currentDictInfo = getString(R.string.noDictLoaded);
326         } else {
327           final LanguageData lang0 = dictionary.languageDatas[0];
328           final LanguageData lang1 = dictionary.languageDatas[1];
329           currentDictInfo = getString(R.string.aboutText, dictionary.dictionaryInfo, dictionary.entries.size(), 
330               lang0.language.symbol, lang0.sortedIndex.size(), lang0.rows.size(),
331               lang1.language.symbol, lang1.sortedIndex.size(), lang1.rows.size());
332         }
333         intent.putExtra(AboutActivity.CURRENT_DICT_INFO, currentDictInfo
334             .toString());
335         startActivity(intent);
336         return false;
337       }
338     });
339
340     final MenuItem download = menu.add(getString(R.string.downloadDictionary));
341     download.setOnMenuItemClickListener(new OnMenuItemClickListener() {
342       public boolean onMenuItemClick(final MenuItem menuItem) {
343         prefsMightHaveChanged = true;
344         startDownloadDictActivity(DictionaryActivity.this);
345         return false;
346       }
347     });
348
349     return true;
350   }
351
352   @Override
353   public boolean onPrepareOptionsMenu(final Menu menu) {
354     switchLanguageMenuItem.setTitle(getString(R.string.switchToLanguage,
355         dictionary.languageDatas[SimpleEntry
356             .otherLang(languageList.languageData.lang)].language.symbol));
357     return super.onPrepareOptionsMenu(menu);
358   }
359   
360   void updateLangButton() {
361     langButton.setText(languageList.languageData.language.symbol);
362   }
363
364   // ----------------------------------------------------------------
365   // Event handlers.
366   // ----------------------------------------------------------------
367   
368   void onLanguageButton() {
369     waitForSearchEnd();
370     languageList = new LanguageListAdapter(
371         dictionary.languageDatas[(languageList.languageData == dictionary.languageDatas[0]) ? 1
372             : 0]);
373     Log.d(LOG, "onLanguageButton, newLang=" + languageList.languageData.language.symbol);
374     setListAdapter(languageList);
375     updateLangButton();
376     onSearchTextChange(searchText.getText().toString());
377   }
378
379   void onUpButton() {
380     final int destRowIndex = languageList.languageData.getPrevTokenRow(getSelectedRow());
381     Log.d(LOG, "onUpButton, destRowIndex=" + destRowIndex);
382     jumpToRow(languageList, destRowIndex);
383   }
384
385   void onDownButton() {
386     final int destRowIndex = languageList.languageData.getNextTokenRow(getSelectedRow());
387     Log.d(LOG, "onDownButton, destRowIndex=" + destRowIndex);
388     jumpToRow(languageList, destRowIndex);
389   }
390
391   void onAppendToWordList() {
392     final int row = getSelectedRow();
393     if (row < 0) {
394       return;
395     }
396     final StringBuilder rawText = new StringBuilder();
397     final String word = languageList.languageData.getIndexEntryForRow(row).word;
398     rawText.append(
399         new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").format(new Date()))
400         .append("\t");
401     rawText.append(word).append("\t");
402     rawText.append(getSelectedRowRawText(saveOnlyFirstSubentry));
403     Log.d(LOG, "Writing : " + rawText);
404     try {
405       wordList.getParentFile().mkdirs();
406       final PrintWriter out = new PrintWriter(
407           new FileWriter(wordList, true));
408       out.println(rawText.toString());
409       out.close();
410     } catch (IOException e) {
411       Log.e(LOG, "Unable to append to " + wordList.getAbsolutePath(), e);
412       Toast.makeText(this, getString(R.string.failedAddingToWordList, wordList.getAbsolutePath()), Toast.LENGTH_LONG);
413     }
414     return;
415   }
416
417   void onCopy() {
418     final int row = getSelectedRow();
419     if (row < 0) {
420       return;
421     }
422     Log.d(LOG, "Copy." + DictionaryActivity.this.getSelectedRow());
423     final StringBuilder result = new StringBuilder();
424     result.append(getSelectedRowRawText(false));
425     final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
426     clipboardManager.setText(result.toString());
427     Log.d(LOG, "Copied: " + result);
428   }
429
430   @Override
431   public boolean onKeyDown(int keyCode, KeyEvent event) {
432     if (event.getUnicodeChar() != 0) {
433       if (!searchText.hasFocus()) {
434         searchText.setText("" + (char) event.getUnicodeChar());
435         onSearchTextChange(searchText.getText().toString());
436         searchText.requestFocus();
437       }
438       return true;
439     }
440     return super.onKeyDown(keyCode, event);
441   }
442
443   @Override
444   protected void onListItemClick(ListView l, View v, int row, long id) {
445     setSelectedRow(row);
446     openContextMenu(getListView());
447   }
448
449   void onSearchTextChange(final String searchText) {
450     Log.d(LOG, "onSearchTextChange: " + searchText);
451     synchronized (this) {
452       searchOperation = new SearchOperation(languageList, searchText.trim(), searchOperation);
453       searchExecutor.execute(searchOperation);
454     }
455   }
456
457   private void onClearSearchTextButton(final Button clearSearchTextButton) {
458     clearSearchTextButton.requestFocus();
459     searchText.setText("");
460     searchText.requestFocus();
461     Log.d(LOG, "Trying to show soft keyboard.");
462     final InputMethodManager manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
463     manager.showSoftInput(searchText, InputMethodManager.SHOW_IMPLICIT);
464   }
465
466   // ----------------------------------------------------------------
467   // ContextMenu
468   // ----------------------------------------------------------------
469
470   @Override
471   public void onCreateContextMenu(ContextMenu menu, View v,
472       ContextMenuInfo menuInfo) {
473     final int row = getSelectedRow();
474     if (row < 0) {
475       return;
476     }
477
478     final MenuItem addToWordlist = menu.add(getString(R.string.addToWordList, wordList.getName()));
479     addToWordlist.setOnMenuItemClickListener(new OnMenuItemClickListener() {
480       public boolean onMenuItemClick(MenuItem item) {
481         onAppendToWordList();
482         return false;
483       }
484     });
485
486     final MenuItem copy = menu.add(android.R.string.copy);
487     copy.setOnMenuItemClickListener(new OnMenuItemClickListener() {
488       public boolean onMenuItemClick(MenuItem item) {
489         onCopy();
490         return false;
491       }
492     });
493
494   }
495
496   private void jumpToRow(final LanguageListAdapter dictionaryListAdapter,
497       final int rowIndex) {
498     Log.d(LOG, "jumpToRow: " + rowIndex);
499     if (dictionaryListAdapter != this.languageList) {
500       Log.w(LOG, "skipping jumpToRow for old list adapter: " + rowIndex);
501       return;
502     }
503     setSelection(rowIndex);
504     setSelectedRow(rowIndex);
505     getListView().setSelected(true);
506   }
507   
508   // TODO: delete me somehow.
509   private int getSelectedRow() {
510     return lastSelectedRow;
511   }
512   private void setSelectedRow(final int row) {
513     lastSelectedRow = row;
514     Log.d(LOG, "Selected: " + getSelectedRowRawText(true));
515     updateSearchText();
516   }
517
518   private void updateSearchText() {
519     Log.d(LOG, "updateSearchText");
520     final int selectedRowIndex = getSelectedRow();
521     if (!searchText.hasFocus()) {
522       if (selectedRowIndex >= 0) {
523         final String word = languageList.languageData
524             .getIndexEntryForRow(selectedRowIndex).word;
525         if (!word.equals(searchText.getText().toString())) {
526           Log.d(LOG, "updateSearchText: setText: " + word);
527           searchText.setText(word);
528         }
529       } else {
530         Log.w(LOG, "updateSearchText: nothing selected.");
531       }
532     }
533   }
534
535   static void startDownloadDictActivity(final Context context) {
536     final Intent intent = new Intent(context, DownloadActivity.class);
537     final SharedPreferences prefs = PreferenceManager
538         .getDefaultSharedPreferences(context);
539     final String dictFetchUrl = prefs.getString(context
540         .getString(R.string.dictFetchUrlKey), context
541         .getString(R.string.dictFetchUrlDefault));
542     final String dictFileName = prefs.getString(context
543         .getString(R.string.dictFileKey), context
544         .getString(R.string.dictFileDefault));
545     intent.putExtra(DownloadActivity.SOURCE, dictFetchUrl);
546     intent.putExtra(DownloadActivity.DEST, dictFileName);
547     context.startActivity(intent);
548   }
549
550   class LanguageListAdapter extends BaseAdapter {
551
552     // Visible for testing.
553     final LanguageData languageData;
554
555     LanguageListAdapter(final LanguageData languageData) {
556       this.languageData = languageData;
557     }
558
559     public int getCount() {
560       return languageData.rows.size();
561     }
562
563     public Dictionary.Row getItem(int rowIndex) {
564       assert rowIndex < languageData.rows.size();
565       return languageData.rows.get(rowIndex);
566     }
567
568     public long getItemId(int rowIndex) {
569       return rowIndex;
570     }
571
572     public View getView(final int rowIndex, final View convertView,
573         final ViewGroup parent) {
574       final Row row = getItem(rowIndex);
575
576       // Token row.
577       if (row.isToken()) {
578         TextView result = null;
579         if (convertView instanceof TextView) {
580           result = (TextView) convertView;
581         } else {
582           result = new TextView(parent.getContext());
583         }
584         if (row == null) {
585           return result;
586         }
587         result.setText(languageData.rowToString(row, false));
588         result.setTextAppearance(parent.getContext(),
589             android.R.style.TextAppearance_Large);
590         result.setClickable(false);
591         return result;
592       }
593
594       // Entry row(s).
595       final TableLayout result = new TableLayout(parent.getContext());
596
597       final SimpleEntry entry = new SimpleEntry(null, null);//.entries.get(row.getIndex());
598       final int rowCount = entry.getRowCount();
599       for (int r = 0; r < rowCount; ++r) {
600         final TableRow tableRow = new TableRow(result.getContext());
601
602         TextView column1 = new TextView(tableRow.getContext());
603         TextView column2 = new TextView(tableRow.getContext());
604         final TableRow.LayoutParams layoutParams = new TableRow.LayoutParams();
605         layoutParams.weight = 0.5f;
606
607         if (r > 0) {
608           final TextView spacer = new TextView(tableRow.getContext());
609           spacer.setText(r == 0 ? "� " : " � ");
610           tableRow.addView(spacer);
611         }
612         tableRow.addView(column1, layoutParams);
613         if (r > 0) {
614           final TextView spacer = new TextView(tableRow.getContext());
615           spacer.setText(r == 0 ? "� " : " � ");
616           tableRow.addView(spacer);
617         }
618         tableRow.addView(column2, layoutParams);
619
620         column1.setWidth(1);
621         column2.setWidth(1);
622         // column1.setTextAppearance(parent.getContext(), android.R.style.Text);
623
624         // TODO: color words by gender
625         final String col1Text = entry.getAllText(languageData.lang)[r];
626         column1.setText(col1Text, TextView.BufferType.SPANNABLE);
627         final Spannable col1Spannable = (Spannable) column1.getText();
628         int startPos = 0;
629         final String token = languageData.getIndexEntryForRow(rowIndex).word;
630         while ((startPos = col1Text.indexOf(token, startPos)) != -1) {
631           col1Spannable.setSpan(new StyleSpan(Typeface.BOLD), startPos,
632               startPos + token.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
633           startPos += token.length();
634         }
635
636         column2.setText(
637             entry.getAllText(SimpleEntry.otherLang(languageData.lang))[r],
638             TextView.BufferType.NORMAL);
639
640         result.addView(tableRow);
641       }
642
643       return result;
644     }
645
646   } // DictionaryListAdapter
647
648   private final class SearchOperation implements Runnable {
649     SearchOperation previousSearchOperation;
650     
651     final LanguageListAdapter listAdapter;
652     final LanguageData languageData;
653     final String searchText;
654     final AtomicBoolean interrupted = new AtomicBoolean(false);
655     boolean searchFinished = false;
656
657     SearchOperation(final LanguageListAdapter listAdapter,
658         final String searchText, final SearchOperation previousSearchOperation) {
659       this.listAdapter = listAdapter;
660       this.languageData = listAdapter.languageData;
661       this.searchText = searchText;
662       this.previousSearchOperation = previousSearchOperation;
663     }
664
665     public void run() {
666       if (previousSearchOperation != null) {
667         previousSearchOperation.stopAndWait();
668       }
669       previousSearchOperation = null;
670       
671       Log.d(LOG, "SearchOperation: " + searchText);
672       final int indexLocation = languageData.lookup(searchText, interrupted);
673       if (!interrupted.get()) {
674         final IndexEntry indexEntry = languageData.sortedIndex.get(indexLocation);
675         
676         Log.d(LOG, "SearchOperation completed: " + indexEntry.toString());
677         uiHandler.post(new Runnable() {
678           public void run() {
679             // Check is just a performance operation.
680             if (!interrupted.get()) {
681               // This is safe, because it checks that the listAdapter hasn't changed.
682               jumpToRow(listAdapter, indexEntry.startRow);
683             }
684             synchronized (DictionaryActivity.this) {
685               searchOperation = null;
686               DictionaryActivity.this.notifyAll();
687             }
688           }
689         });
690       }      
691       synchronized (this) {
692         searchFinished = true;
693         this.notifyAll();
694       }
695     }
696     
697     private void stopAndWait() {
698       interrupted.set(true);
699       synchronized (this) {
700         while (!searchFinished) {
701           Log.d(LOG, "stopAndWait: " + searchText);
702           try {
703             this.wait();
704           } catch (InterruptedException e) {
705             Log.e(LOG, "Interrupted", e);
706           }
707         }
708       }
709     }
710   }  // SearchOperation
711
712   void waitForSearchEnd() {
713     synchronized (this) {
714       while (searchOperation != null) {
715         Log.d(LOG, "waitForSearchEnd");
716         try {
717           this.wait();
718         } catch (InterruptedException e) {
719           Log.e(LOG, "Interrupted.", e);
720         }
721       }
722     }
723   }
724
725   private class SearchTextWatcher implements TextWatcher {
726     public void afterTextChanged(final Editable searchTextEditable) {
727       Log.d(LOG, "Search text changed: " + searchText.getText().toString());
728       if (searchText.hasFocus()) {
729         // If they were typing to cause the change, update the UI.
730         onSearchTextChange(searchText.getText().toString());
731       }
732     }
733
734     public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,
735         int arg3) {
736     }
737
738     public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
739     }
740   }
741
742 }