+ // Don't auto-launch if this fails.
+ prefs.edit().remove(C.DICT_FILE).remove(C.INDEX_SHORT_NAME).commit();
+
+ setContentView(R.layout.dictionary_activity);
+
+ theme = application.getSelectedTheme();
+ textColorFg = getResources().getColor(theme.tokenRowFgColor);
+
+ if (dictRaf != null) {
+ try {
+ dictRaf.close();
+ } catch (IOException e) {
+ Log.e(LOG, "Failed to close dictionary", e);
+ }
+ dictRaf = null;
+ }
+
+ final Intent intent = getIntent();
+ String intentAction = intent.getAction();
+ /*
+ @author Dominik Köppl Querying the Intent
+ * com.hughes.action.ACTION_SEARCH_DICT is the advanced query
+ * Arguments: SearchManager.QUERY -> the phrase to search from
+ * -> language in which the phrase is written to -> to which
+ * language shall be translated
+ */
+ if ("com.hughes.action.ACTION_SEARCH_DICT".equals(intentAction)) {
+ String query = intent.getStringExtra(SearchManager.QUERY);
+ String from = intent.getStringExtra("from");
+ if (from != null)
+ from = from.toLowerCase(Locale.US);
+ String to = intent.getStringExtra("to");
+ if (to != null)
+ to = to.toLowerCase(Locale.US);
+ if (query != null) {
+ getIntent().putExtra(C.SEARCH_TOKEN, query);
+ }
+ if (intent.getStringExtra(C.DICT_FILE) == null && (from != null || to != null)) {
+ Log.d(LOG, "DictSearch: from: " + from + " to " + to);
+ List<DictionaryInfo> dicts = application.getDictionariesOnDevice(null);
+ for (DictionaryInfo info : dicts) {
+ boolean hasFrom = from == null;
+ boolean hasTo = to == null;
+ for (IndexInfo index : info.indexInfos) {
+ if (!hasFrom && index.shortName.toLowerCase(Locale.US).equals(from))
+ hasFrom = true;
+ if (!hasTo && index.shortName.toLowerCase(Locale.US).equals(to))
+ hasTo = true;
+ }
+ if (hasFrom && hasTo) {
+ if (from != null) {
+ int which_index = 0;
+ for (; which_index < info.indexInfos.size(); ++which_index) {
+ if (info.indexInfos.get(which_index).shortName.toLowerCase(
+ Locale.US).equals(from))
+ break;
+ }
+ intent.putExtra(C.INDEX_SHORT_NAME,
+ info.indexInfos.get(which_index).shortName);
+
+ }
+ intent.putExtra(C.DICT_FILE, application.getPath(info.uncompressedFilename)
+ .toString());
+ break;
+ }
+ }
+
+ }
+ }
+ /*
+ @author Dominik Köppl Querying the Intent Intent.ACTION_SEARCH is a
+ * simple query Arguments follow from android standard (see
+ * documentation)
+ */
+ if (intentAction != null && intentAction.equals(Intent.ACTION_SEARCH)) {
+ String query = intent.getStringExtra(SearchManager.QUERY);
+ if (query != null)
+ getIntent().putExtra(C.SEARCH_TOKEN, query);
+ }
+ if (intentAction != null && intentAction.equals(Intent.ACTION_SEND)) {
+ String query = intent.getStringExtra(Intent.EXTRA_TEXT);
+ if (query != null)
+ getIntent().putExtra(C.SEARCH_TOKEN, query);
+ }
+ /*
+ * This processes text on M+ devices where QuickDic shows up in the context menu.
+ */
+ if (intentAction != null && intentAction.equals(Intent.ACTION_PROCESS_TEXT)) {
+ String query = intent.getStringExtra(Intent.EXTRA_PROCESS_TEXT);
+ if (query != null) {
+ getIntent().putExtra(C.SEARCH_TOKEN, query);
+ }
+ }
+ // Support opening dictionary file directly
+ if (intentAction != null && intentAction.equals(Intent.ACTION_VIEW)) {
+ Uri uri = intent.getData();
+ intent.putExtra(C.DICT_FILE, uri.toString());
+ dictFileTitleName = uri.getLastPathSegment();
+ try {
+ dictRaf = getContentResolver().openAssetFileDescriptor(uri, "r").createInputStream().getChannel();
+ } catch (Exception e) {
+ dictionaryOpenFail(e);
+ return;
+ }
+ }
+ /*
+ @author Dominik Köppl If no dictionary is chosen, use the default
+ * dictionary specified in the preferences If this step does
+ * fail (no default dictionary specified), show a toast and
+ * abort.
+ */
+ if (intent.getStringExtra(C.DICT_FILE) == null) {
+ String dictfile = prefs.getString(getString(R.string.defaultDicKey), null);
+ if (dictfile != null)
+ intent.putExtra(C.DICT_FILE, application.getPath(dictfile).toString());
+ }
+ String dictFilename = intent.getStringExtra(C.DICT_FILE);
+ if (dictFilename == null && intent.getStringExtra(C.SEARCH_TOKEN) != null) {
+ final List<DictionaryInfo> dics = application.getDictionariesOnDevice(null);
+ final String search = intent.getStringExtra(C.SEARCH_TOKEN);
+ String bestFname = null;
+ String bestIndex = null;
+ int bestMatchLen = 2; // ignore shorter matches
+ AtomicBoolean dummy = new AtomicBoolean();
+ for (int i = 0; dictFilename == null && i < dics.size(); ++i) {
+ try {
+ Log.d(LOG, "Checking dictionary " + dics.get(i).uncompressedFilename);
+ final File dictfile = application.getPath(dics.get(i).uncompressedFilename);
+ Dictionary dic = new Dictionary(new RandomAccessFile(dictfile, "r").getChannel());
+ for (int j = 0; j < dic.indices.size(); ++j) {
+ Index idx = dic.indices.get(j);
+ Log.d(LOG, "Checking index " + idx.shortName);
+ if (idx.findExact(search) != null) {
+ Log.d(LOG, "Found exact match");
+ dictFilename = dictfile.toString();
+ intent.putExtra(C.INDEX_SHORT_NAME, idx.shortName);
+ break;
+ }
+ int matchLen = getMatchLen(search, idx.findInsertionPoint(search, dummy));
+ Log.d(LOG, "Found partial match length " + matchLen);
+ if (matchLen > bestMatchLen) {
+ bestFname = dictfile.toString();
+ bestIndex = idx.shortName;
+ bestMatchLen = matchLen;
+ }
+ }
+ } catch (Exception e) {}
+ }
+ if (dictFilename == null && bestFname != null) {
+ dictFilename = bestFname;
+ intent.putExtra(C.INDEX_SHORT_NAME, bestIndex);
+ }
+ }
+
+ if (dictFilename == null) {
+ if (!isFinishing())
+ Toast.makeText(this, getString(R.string.no_dict_file), Toast.LENGTH_LONG).show();
+ startActivity(DictionaryManagerActivity.getLaunchIntent(getApplicationContext()));
+ finish();
+ return;
+ }
+ if (dictRaf == null)
+ dictFile = new File(dictFilename);
+
+ ttsReady = false;
+ textToSpeech = new TextToSpeech(getApplicationContext(), new OnInitListener() {
+ @Override
+ public void onInit(int status) {
+ ttsReady = true;
+ updateTTSLanguage(indexIndex);
+ }
+ });
+
+ try {
+ if (dictRaf == null) {
+ dictFileTitleName = application.getDictionaryName(dictFile.getName());
+ dictRaf = new RandomAccessFile(dictFile, "r").getChannel();
+ }
+ this.setTitle("QuickDic: " + dictFileTitleName);
+ dictionary = new Dictionary(dictRaf);
+ } catch (Exception e) {
+ dictionaryOpenFail(e);
+ return;
+ }
+ String targetIndex = intent.getStringExtra(C.INDEX_SHORT_NAME);
+ if (savedInstanceState != null && savedInstanceState.getString(C.INDEX_SHORT_NAME) != null) {
+ targetIndex = savedInstanceState.getString(C.INDEX_SHORT_NAME);
+ }
+ indexIndex = 0;
+ for (int i = 0; i < dictionary.indices.size(); ++i) {
+ if (dictionary.indices.get(i).shortName.equals(targetIndex)) {
+ indexIndex = i;
+ break;
+ }
+ }
+ Log.d(LOG, "Loading index " + indexIndex);
+ index = dictionary.indices.get(indexIndex);
+ getListView().setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+ getListView().setEmptyView(findViewById(android.R.id.empty));
+ getListView().setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int row, long id) {
+ onListItemClick(getListView(), view, row, id);
+ }
+ });
+
+ setListAdapter(new IndexAdapter(index));
+
+ // Pre-load the Transliterator (will spawn its own thread)
+ TransliteratorManager.init(new TransliteratorManager.Callback() {
+ @Override
+ public void onTransliteratorReady() {
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onSearchTextChange(searchView.getQuery().toString());
+ }
+ });
+ }
+ }, DictionaryApplication.threadBackground);
+
+ // Pre-load the collators.
+ new Thread(new Runnable() {
+ public void run() {
+ android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE);
+ final long startMillis = System.currentTimeMillis();
+ try {
+ for (final Index index : dictionary.indices) {
+ final String searchToken = index.sortedIndexEntries.get(0).token;
+ final IndexEntry entry = index.findExact(searchToken);
+ if (entry == null || !searchToken.equals(entry.token)) {
+ Log.e(LOG, "Couldn't find token: " + searchToken + ", " + (entry == null ? "null" : entry.token));
+ }
+ }
+ indexPrepFinished = true;
+ } catch (Exception e) {
+ Log.w(LOG,
+ "Exception while prepping. This can happen if dictionary is closed while search is happening.");
+ }
+ Log.d(LOG, "Prepping indices took:" + (System.currentTimeMillis() - startMillis));
+ }
+ }).start();
+
+ String fontName = prefs.getString(getString(R.string.fontKey), "FreeSerif.otf.jpg");
+ switch (fontName) {
+ case "SYSTEM":
+ typeface = Typeface.DEFAULT;
+ break;
+ case "SERIF":
+ typeface = Typeface.SERIF;
+ break;
+ case "SANS_SERIF":
+ typeface = Typeface.SANS_SERIF;
+ break;
+ case "MONOSPACE":
+ typeface = Typeface.MONOSPACE;
+ break;
+ default:
+ if ("FreeSerif.ttf.jpg".equals(fontName)) {
+ fontName = "FreeSerif.otf.jpg";
+ }
+ try {
+ typeface = Typeface.createFromAsset(getAssets(), fontName);
+ } catch (Exception e) {
+ Log.w(LOG, "Exception trying to use typeface, using default.", e);
+ if (!isFinishing())
+ Toast.makeText(this, getString(R.string.fontFailure, e.getLocalizedMessage()),
+ Toast.LENGTH_LONG).show();
+ }
+ break;
+ }
+ if (typeface == null) {
+ Log.w(LOG, "Unable to create typeface, using default.");
+ typeface = Typeface.DEFAULT;
+ }
+ final String fontSize = prefs.getString(getString(R.string.fontSizeKey), "14");
+ try {
+ fontSizeSp = Integer.parseInt(fontSize.trim());
+ } catch (NumberFormatException e) {
+ fontSizeSp = 14;
+ }
+
+ // ContextMenu.
+ registerForContextMenu(getListView());
+
+ // Cache some prefs.
+ wordList = application.getWordListFile();
+ saveOnlyFirstSubentry = prefs.getBoolean(getString(R.string.saveOnlyFirstSubentryKey),
+ false);
+ clickOpensContextMenu = prefs.getBoolean(getString(R.string.clickOpensContextMenuKey),
+ !getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN));
+ Log.d(LOG, "wordList=" + wordList + ", saveOnlyFirstSubentry=" + saveOnlyFirstSubentry);
+
+ onCreateSetupActionBarAndSearchView();
+
+ View floatSwapButton = findViewById(R.id.floatSwapButton);
+ floatSwapButton.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ onLanguageButtonLongClick(v.getContext());
+ return true;
+ }
+ });
+
+ // Set the search text from the intent, then the saved state.
+ String text = getIntent().getStringExtra(C.SEARCH_TOKEN);
+ if (savedInstanceState != null) {
+ text = savedInstanceState.getString(C.SEARCH_TOKEN);
+ }
+ if (text == null) {
+ text = "";
+ }
+ setSearchText(text, true);
+ Log.d(LOG, "Trying to restore searchText=" + text);
+
+ setDictionaryPrefs(this, dictFile, index.shortName);
+
+ updateLangButton();
+ searchView.requestFocus();
+
+ // http://stackoverflow.com/questions/2833057/background-listview-becomes-black-when-scrolling
+// getListView().setCacheColorHint(0);
+ }
+
+ private void onCreateSetupActionBarAndSearchView() {
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayShowTitleEnabled(false);
+ actionBar.setDisplayShowHomeEnabled(false);
+ actionBar.setDisplayHomeAsUpEnabled(false);
+
+ final LinearLayout customSearchView = new LinearLayout(getSupportActionBar().getThemedContext());
+
+ final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ customSearchView.setLayoutParams(layoutParams);
+
+ languageButton = new ImageButton(customSearchView.getContext());
+ languageButton.setId(R.id.languageButton);
+ languageButton.setScaleType(ScaleType.FIT_CENTER);
+ languageButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onLanguageButtonLongClick(v.getContext());
+ }
+ });
+ languageButton.setAdjustViewBounds(true);
+ LinearLayout.LayoutParams lpb = new LinearLayout.LayoutParams(application.languageButtonPixels, LinearLayout.LayoutParams.MATCH_PARENT);
+ customSearchView.addView(languageButton, lpb);
+
+ searchView = new SearchView(getSupportActionBar().getThemedContext());
+ searchView.setId(R.id.searchView);
+
+ // Get rid of search icon, it takes up too much space.
+ // There is still text saying "search" in the search field.
+ searchView.setIconifiedByDefault(true);
+ searchView.setIconified(false);
+
+ searchView.setQueryHint(getString(R.string.searchText));
+ searchView.setSubmitButtonEnabled(false);
+ searchView.setInputType(InputType.TYPE_CLASS_TEXT);
+ searchView.setImeOptions(
+ EditorInfo.IME_ACTION_DONE |
+ EditorInfo.IME_FLAG_NO_EXTRACT_UI |
+ // EditorInfo.IME_FLAG_NO_FULLSCREEN | // Requires API
+ // 11
+ EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ onQueryTextListener = new OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ Log.d(LOG, "OnQueryTextListener: onQueryTextSubmit: " + searchView.getQuery());
+ hideKeyboard();
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ Log.d(LOG, "OnQueryTextListener: onQueryTextChange: " + searchView.getQuery());
+ onSearchTextChange(searchView.getQuery().toString());
+ return true;
+ }
+ };
+ searchView.setOnQueryTextListener(onQueryTextListener);
+ searchView.setFocusable(true);
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0,
+ FrameLayout.LayoutParams.WRAP_CONTENT, 1);
+ customSearchView.addView(searchView, lp);
+
+ actionBar.setCustomView(customSearchView);
+ actionBar.setDisplayShowCustomEnabled(true);
+
+ // Avoid wasting space on large left inset
+ Toolbar tb = (Toolbar)customSearchView.getParent();
+ tb.setContentInsetsRelative(0, 0);
+
+ getListView().setNextFocusLeftId(R.id.searchView);
+ findViewById(R.id.floatSwapButton).setNextFocusRightId(R.id.languageButton);
+ languageButton.setNextFocusLeftId(R.id.floatSwapButton);
+ }
+
+ @Override
+ protected void onResume() {
+ Log.d(LOG, "onResume");
+ super.onResume();
+ if (PreferenceActivity.prefsMightHaveChanged) {
+ PreferenceActivity.prefsMightHaveChanged = false;
+ finish();
+ startActivity(getIntent());
+ }
+ showKeyboard();
+ }
+
+ /**
+ * Invoked when MyWebView returns, since the user might have clicked some
+ * hypertext in the MyWebView.
+ */
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent result) {
+ super.onActivityResult(requestCode, resultCode, result);
+ if (result != null && result.hasExtra(C.SEARCH_TOKEN)) {
+ Log.d(LOG, "onActivityResult: " + result.getStringExtra(C.SEARCH_TOKEN));
+ jumpToTextFromHyperLink(result.getStringExtra(C.SEARCH_TOKEN), indexIndex);
+ }
+ }
+
+ private static void setDictionaryPrefs(final Context context, final File dictFile,
+ final String indexShortName) {
+ final SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(
+ context).edit();
+ if (dictFile != null) {
+ prefs.putString(C.DICT_FILE, dictFile.getPath());
+ prefs.putString(C.INDEX_SHORT_NAME, indexShortName);
+ }
+ prefs.remove(C.SEARCH_TOKEN); // Don't need to save search token.
+ prefs.commit();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (dictRaf == null) {
+ return;
+ }
+
+ final SearchOperation searchOperation = currentSearchOperation;
+ currentSearchOperation = null;
+
+ // Before we close the RAF, we have to wind the current search down.
+ if (searchOperation != null) {
+ Log.d(LOG, "Interrupting search to shut down.");
+ currentSearchOperation = null;
+ searchOperation.interrupted.set(true);
+ }
+ searchExecutor.shutdownNow();
+ textToSpeech.shutdown();
+ textToSpeech = null;
+
+ indexAdapter = null;
+ setListAdapter(null);
+
+ try {
+ Log.d(LOG, "Closing RAF.");
+ dictRaf.close();
+ } catch (IOException e) {
+ Log.e(LOG, "Failed to close dictionary", e);
+ }
+ dictRaf = null;
+ }
+
+ // --------------------------------------------------------------------------
+ // Buttons
+ // --------------------------------------------------------------------------
+
+ private void showKeyboard() {
+ // For some reason, this doesn't always work the first time.
+ // One way to replicate the problem:
+ // Press the "task switch" button repeatedly to pause and resume
+ for (int delay = 1; delay <= 101; delay += 100) {
+ searchView.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Log.d(LOG, "Trying to show soft keyboard.");
+ final boolean searchTextHadFocus = searchView.hasFocus();
+ searchView.requestFocusFromTouch();
+ final InputMethodManager manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ manager.showSoftInput(searchView, InputMethodManager.SHOW_IMPLICIT);
+ if (!searchTextHadFocus) {
+ defocusSearchText();
+ }
+ }
+ }, delay);
+ }
+ }
+
+ private void hideKeyboard() {
+ Log.d(LOG, "Hide soft keyboard.");
+ searchView.clearFocus();
+ InputMethodManager manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ manager.hideSoftInputFromWindow(searchView.getWindowToken(), 0);
+ }
+
+ private void updateLangButton() {
+ final int flagId = IsoUtils.INSTANCE.getFlagIdForIsoCode(index.shortName);
+ if (flagId != 0) {
+ languageButton.setImageResource(flagId);