| // Copyright 2010-2014, Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| package org.mozc.android.inputmethod.japanese.userdictionary; |
| |
| import org.mozc.android.inputmethod.japanese.MozcUtil; |
| import org.mozc.android.inputmethod.japanese.protobuf.ProtoUserDictionaryStorage.UserDictionary.Entry; |
| import org.mozc.android.inputmethod.japanese.protobuf.ProtoUserDictionaryStorage.UserDictionary.PosType; |
| import org.mozc.android.inputmethod.japanese.protobuf.ProtoUserDictionaryStorage.UserDictionaryCommandStatus.Status; |
| import org.mozc.android.inputmethod.japanese.resources.R; |
| import com.google.common.base.Preconditions; |
| |
| import android.app.AlertDialog; |
| import android.app.Dialog; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.DialogInterface.OnDismissListener; |
| import android.content.Intent; |
| import android.content.res.Resources; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Message; |
| import android.util.AttributeSet; |
| import android.util.DisplayMetrics; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.WindowManager; |
| import android.view.WindowManager.LayoutParams; |
| import android.view.inputmethod.InputMethodManager; |
| import android.widget.ArrayAdapter; |
| import android.widget.EditText; |
| import android.widget.FrameLayout; |
| import android.widget.ScrollView; |
| import android.widget.Spinner; |
| import android.widget.TextView; |
| |
| import java.io.IOException; |
| import java.io.RandomAccessFile; |
| import java.io.UnsupportedEncodingException; |
| import java.nio.ByteBuffer; |
| import java.nio.CharBuffer; |
| import java.nio.channels.FileChannel; |
| import java.nio.channels.FileChannel.MapMode; |
| import java.nio.charset.Charset; |
| import java.nio.charset.CodingErrorAction; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.EnumMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Utilities (of, especially, UI related stuff) for the user dictionary tool. |
| * |
| */ |
| class UserDictionaryUtil { |
| |
| /** |
| * Callback which is called when the "positive button" on a dialog is clicked. |
| */ |
| private interface UserDictionaryBaseDialogListener { |
| public Status onPositiveButtonClicked(View view); |
| } |
| |
| /** |
| * Base implementation of popup dialog on user dictionary tool. |
| * |
| * This class has: |
| * <ul> |
| * <li>Title messaging |
| * <li>Content view based on the given resource id |
| * <li>Cancel and OK buttons |
| * </ul> |
| * |
| * When the OK button is clicked, UserDictionaryBaseDialogListener callback is invoked. |
| * The expected usage is invoke something action by interacting with the mozc server. |
| * If the interaction fails (in more precise, the returned status is not |
| * USER_DICTIONARY_COMMAND_SUCCESS), this class will show a toast message, and the popup dialog |
| * won't be dismissed. |
| */ |
| private static class UserDictionaryBaseDialog extends AlertDialog { |
| private final UserDictionaryBaseDialogListener listener; |
| private final ToastManager toastManager; |
| |
| UserDictionaryBaseDialog(Context context, int titleResourceId, int viewResourceId, |
| UserDictionaryBaseDialogListener listener, |
| OnDismissListener dismissListener, |
| ToastManager toastManager) { |
| super(context); |
| this.listener = listener; |
| this.toastManager = toastManager; |
| |
| // Initialize the view. Set the title, the content view and ok, cancel buttons. |
| setTitle(titleResourceId); |
| setView(LayoutInflater.from(context).inflate(viewResourceId, null)); |
| |
| // Set a dummy Message instance to fix crashing issue on Android 2.1. |
| // On Android 2.1, com.android.internal.app.AlertController wrongly checks the message |
| // for the availability of button view. So without the dummy message, |
| // getButton(BUTTON_POSITIVE) used in onCreate would return null. |
| setButton(DialogInterface.BUTTON_POSITIVE, context.getText(android.R.string.ok), |
| Message.obtain()); |
| setButton(DialogInterface.BUTTON_NEGATIVE, context.getText(android.R.string.cancel), |
| DialogInterface.OnClickListener.class.cast(null)); |
| setCancelable(true); |
| setOnDismissListener(dismissListener); |
| } |
| |
| @Override |
| protected void onCreate(Bundle savedInstance) { |
| super.onCreate(savedInstance); |
| |
| ViewGroup contentGroup = ViewGroup.class.cast(findViewById(android.R.id.content)); |
| if (contentGroup != null && contentGroup.getChildCount() > 0) { |
| // Wrap the content view by ScrollView so that we can scroll the dialog window |
| // in order to avoid shrinking the main edit text fields. |
| ScrollView view = ScrollView.class.cast( |
| LayoutInflater.from(getContext()).inflate( |
| R.layout.user_dictionary_tool_empty_scrollview, null)); |
| View contentView = contentGroup.getChildAt(0); |
| contentGroup.removeViewAt(0); |
| FrameLayout.class.cast( |
| view.findViewById(R.id.user_dictionary_tool_empty_scroll_view_content)) |
| .addView(contentView); |
| contentGroup.addView(view, 0); |
| } |
| |
| // To override the default behavior that the dialog is dismissed after user's clicking |
| // a button regardless of any action inside listener, we set the callback directly |
| // to the button and manage dismissing behavior. |
| // Note that it is necessary to do this here, instead of in the constructor, |
| // because the UI is initialized in super class's onCreate method, and we cannot obtain |
| // the button until the initialization. |
| getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| Status status = listener.onPositiveButtonClicked(view); |
| toastManager.maybeShowMessageShortly(status); |
| if (status == Status.USER_DICTIONARY_COMMAND_SUCCESS) { |
| // Dismiss the dialog, iff the operation is successfully done. |
| dismiss(); |
| } |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Simple class to show the internationalized POS names on a spinner. |
| */ |
| private static class PosItem { |
| final PosType posType; |
| final String name; |
| |
| PosItem(PosType posType, String name) { |
| this.posType = posType; |
| this.name = name; |
| } |
| |
| // This is a hook to show the appropriate (internationalized) items on spinner. |
| @Override |
| public String toString() { |
| return name; |
| } |
| } |
| |
| /** |
| * Spinner implementation which has a list of POS. |
| * |
| * To keep users out from confusing UI, this class hides the IME when the list dialog is |
| * shown by user's tap. |
| */ |
| public static class PosSpinner extends Spinner { |
| public PosSpinner(Context context) { |
| super(context); |
| } |
| public PosSpinner(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| } |
| public PosSpinner(Context context, AttributeSet attrs, int defStyle) { |
| super(context, attrs, defStyle); |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| // Set adapter containing a list of POS types. |
| ArrayAdapter<PosItem> adapter = new ArrayAdapter<PosItem>( |
| getContext(), android.R.layout.simple_spinner_item, createPosItemList(getResources())); |
| adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); |
| setAdapter(adapter); |
| } |
| |
| private static List<PosItem> createPosItemList(Resources resources) { |
| PosType[] posTypeValues = PosType.values(); |
| List<PosItem> result = new ArrayList<PosItem>(posTypeValues.length); |
| for (PosType posType : posTypeValues) { |
| result.add(new PosItem(posType, |
| resources.getText(getPosStringResourceId(posType)).toString())); |
| } |
| return result; |
| } |
| |
| @Override |
| public boolean performClick() { |
| // When the spinner is tapped (i.e. when the popup dialog is shown), |
| // we hide the soft input method. |
| // This is because the list of the POS is long so if the soft input is shown |
| // continuously, a part of list would be out of the display. |
| InputMethodManager imm = InputMethodManager.class.cast( |
| getContext().getSystemService(Context.INPUT_METHOD_SERVICE)); |
| imm.hideSoftInputFromWindow(getWindowToken(), 0); |
| |
| return super.performClick(); |
| } |
| } |
| |
| interface WordRegisterDialogListener { |
| |
| /** |
| * Callback to be called when the positive button is clicked. |
| * @return result status of the executed command. |
| */ |
| public Status onPositiveButtonClicked(String word, String reading, PosType pos); |
| } |
| |
| /** |
| * Word Register Dialog implementation for adding/editing entries. |
| * |
| * The dialog should have: |
| * <ul> |
| * <li>A EditText for "word" editing, |
| * <li>A EditText for "reading" editing, and |
| * <li>A Spinner for "pos" selecting. |
| */ |
| static class WordRegisterDialog extends UserDictionaryBaseDialog { |
| WordRegisterDialog(Context context, int titleResourceId, |
| final WordRegisterDialogListener listener, |
| OnDismissListener dismissListener, |
| ToastManager toastManager) { |
| super(context, titleResourceId, R.layout.user_dictionary_tool_word_register_dialog_view, |
| new UserDictionaryBaseDialogListener() { |
| @Override |
| public Status onPositiveButtonClicked(View view) { |
| return listener.onPositiveButtonClicked( |
| getText(view, R.id.user_dictionary_tool_word_register_dialog_word), |
| getText(view, R.id.user_dictionary_tool_word_register_dialog_reading), |
| getPos(view, R.id.user_dictionary_tool_word_register_dialog_pos)); |
| } |
| }, |
| dismissListener, toastManager); |
| // TODO(hidehiko): Attach a callback for un-focused event on "word" EditText. |
| // and invoke reverse conversion (if necessary) to fill reading automatically. |
| } |
| |
| /** |
| * Sets the entry so that users can see it when the dialog is shown. |
| * To make editing convenient, select all region. |
| */ |
| void setEntry(Entry entry) { |
| EditText wordEditText = EditText.class.cast( |
| findViewById(R.id.user_dictionary_tool_word_register_dialog_word)); |
| wordEditText.setText(entry.getValue()); |
| EditText.class.cast(findViewById(R.id.user_dictionary_tool_word_register_dialog_reading)) |
| .setText(entry.getKey()); |
| Spinner posSpinner = Spinner.class.cast( |
| findViewById(R.id.user_dictionary_tool_word_register_dialog_pos)); |
| int numItems = posSpinner.getCount(); |
| for (int i = 0; i < numItems; ++i) { |
| if (PosItem.class.cast(posSpinner.getItemAtPosition(i)).posType == entry.getPos()) { |
| posSpinner.setSelection(i); |
| break; |
| } |
| } |
| |
| // Focus "word" field by default. |
| wordEditText.requestFocus(); |
| } |
| } |
| |
| interface DictionaryNameDialogListener { |
| |
| /** |
| * Callback to be called when the positive button is clicked. |
| * @param dictionaryName the text which is filled in EditText on the dialog. |
| * @return result status of the executed command. |
| */ |
| public Status onPositiveButtonClicked(String dictionaryName); |
| } |
| |
| /** |
| * Dialog implementation which has one edit box for dictionary name editing. |
| */ |
| static class DictionaryNameDialog extends UserDictionaryBaseDialog { |
| DictionaryNameDialog(Context context, int titleResourceId, |
| final DictionaryNameDialogListener listener, |
| OnDismissListener dismissListener, |
| ToastManager toastManager) { |
| super(context, titleResourceId, R.layout.user_dictionary_tool_dictionary_name_dialog_view, |
| new UserDictionaryBaseDialogListener() { |
| @Override |
| public Status onPositiveButtonClicked(View view) { |
| return listener.onPositiveButtonClicked( |
| getText(view, R.id.user_dictionary_tool_dictionary_name_dialog_name)); |
| } |
| }, |
| dismissListener, toastManager); |
| } |
| |
| /** |
| * Sets the dictionaryName so that users can see it when the dialog is shown. |
| * To make editing convenient, select all region. |
| */ |
| void setDictionaryName(String dictionaryName) { |
| EditText editText = EditText.class.cast( |
| findViewById(R.id.user_dictionary_tool_dictionary_name_dialog_name)); |
| editText.setText(dictionaryName); |
| editText.selectAll(); |
| } |
| } |
| |
| private static final int DIALOG_WIDTH_THRESHOLD = 480; // in dip. |
| |
| /** A map from PosType to the string resource id for i18n. */ |
| private static final Map<PosType, Integer> POS_RESOURCE_MAP; |
| static { |
| EnumMap<PosType, Integer> map = new EnumMap<PosType, Integer>(PosType.class); |
| map.put(PosType.NOUN, R.string.japanese_pos_noun); |
| map.put(PosType.ABBREVIATION, R.string.japanese_pos_abbreviation); |
| map.put(PosType.SUGGESTION_ONLY, R.string.japanese_pos_suggestion_only); |
| map.put(PosType.PROPER_NOUN, R.string.japanese_pos_proper_noun); |
| map.put(PosType.PERSONAL_NAME, R.string.japanese_pos_personal_name); |
| map.put(PosType.FAMILY_NAME, R.string.japanese_pos_family_name); |
| map.put(PosType.FIRST_NAME, R.string.japanese_pos_first_name); |
| map.put(PosType.ORGANIZATION_NAME, R.string.japanese_pos_organization_name); |
| map.put(PosType.PLACE_NAME, R.string.japanese_pos_place_name); |
| map.put(PosType.SA_IRREGULAR_CONJUGATION_NOUN, |
| R.string.japanese_pos_sa_irregular_conjugation_noun); |
| map.put(PosType.ADJECTIVE_VERBAL_NOUN, R.string.japanese_pos_adjective_verbal_noun); |
| map.put(PosType.NUMBER, R.string.japanese_pos_number); |
| map.put(PosType.ALPHABET, R.string.japanese_pos_alphabet); |
| map.put(PosType.SYMBOL, R.string.japanese_pos_symbol); |
| map.put(PosType.EMOTICON, R.string.japanese_pos_emoticon); |
| map.put(PosType.ADVERB, R.string.japanese_pos_adverb); |
| map.put(PosType.PRENOUN_ADJECTIVAL, R.string.japanese_pos_prenoun_adjectival); |
| map.put(PosType.CONJUNCTION, R.string.japanese_pos_conjunction); |
| map.put(PosType.INTERJECTION, R.string.japanese_pos_interjection); |
| map.put(PosType.PREFIX, R.string.japanese_pos_prefix); |
| map.put(PosType.COUNTER_SUFFIX, R.string.japanese_pos_counter_suffix); |
| map.put(PosType.GENERIC_SUFFIX, R.string.japanese_pos_generic_suffix); |
| map.put(PosType.PERSON_NAME_SUFFIX, R.string.japanese_pos_person_name_suffix); |
| map.put(PosType.PLACE_NAME_SUFFIX, R.string.japanese_pos_place_name_suffix); |
| map.put(PosType.WA_GROUP1_VERB, R.string.japanese_pos_wa_group1_verb); |
| map.put(PosType.KA_GROUP1_VERB, R.string.japanese_pos_ka_group1_verb); |
| map.put(PosType.SA_GROUP1_VERB, R.string.japanese_pos_sa_group1_verb); |
| map.put(PosType.TA_GROUP1_VERB, R.string.japanese_pos_ta_group1_verb); |
| map.put(PosType.NA_GROUP1_VERB, R.string.japanese_pos_na_group1_verb); |
| map.put(PosType.MA_GROUP1_VERB, R.string.japanese_pos_ma_group1_verb); |
| map.put(PosType.RA_GROUP1_VERB, R.string.japanese_pos_ra_group1_verb); |
| map.put(PosType.GA_GROUP1_VERB, R.string.japanese_pos_ga_group1_verb); |
| map.put(PosType.BA_GROUP1_VERB, R.string.japanese_pos_ba_group1_verb); |
| map.put(PosType.HA_GROUP1_VERB, R.string.japanese_pos_ha_group1_verb); |
| map.put(PosType.GROUP2_VERB, R.string.japanese_pos_group2_verb); |
| map.put(PosType.KURU_GROUP3_VERB, R.string.japanese_pos_kuru_group3_verb); |
| map.put(PosType.SURU_GROUP3_VERB, R.string.japanese_pos_suru_group3_verb); |
| map.put(PosType.ZURU_GROUP3_VERB, R.string.japanese_pos_zuru_group3_verb); |
| map.put(PosType.RU_GROUP3_VERB, R.string.japanese_pos_ru_group3_verb); |
| map.put(PosType.ADJECTIVE, R.string.japanese_pos_adjective); |
| map.put(PosType.SENTENCE_ENDING_PARTICLE, R.string.japanese_pos_sentence_ending_particle); |
| map.put(PosType.PUNCTUATION, R.string.japanese_pos_punctuation); |
| map.put(PosType.FREE_STANDING_WORD, R.string.japanese_pos_free_standing_word); |
| map.put(PosType.SUPPRESSION_WORD, R.string.japanese_pos_suppression_word); |
| |
| if (map.size() != PosType.values().length) { |
| // There seems something unknown POS. |
| throw new AssertionError(); |
| } |
| POS_RESOURCE_MAP = Collections.unmodifiableMap(map); |
| } |
| |
| /** A map from PosType to the string resource id dictionary export. */ |
| private static final Map<PosType, Integer> POS_RESOURCE_MAP_FOR_DICTIONARY_EXPORT; |
| static { |
| EnumMap<PosType, Integer> map = new EnumMap<PosType, Integer>(PosType.class); |
| map.put(PosType.NOUN, R.string.japanese_pos_for_dictionary_export_noun); |
| map.put(PosType.ABBREVIATION, R.string.japanese_pos_for_dictionary_export_abbreviation); |
| map.put(PosType.SUGGESTION_ONLY, R.string.japanese_pos_for_dictionary_export_suggestion_only); |
| map.put(PosType.PROPER_NOUN, R.string.japanese_pos_for_dictionary_export_proper_noun); |
| map.put(PosType.PERSONAL_NAME, R.string.japanese_pos_for_dictionary_export_personal_name); |
| map.put(PosType.FAMILY_NAME, R.string.japanese_pos_for_dictionary_export_family_name); |
| map.put(PosType.FIRST_NAME, R.string.japanese_pos_for_dictionary_export_first_name); |
| map.put(PosType.ORGANIZATION_NAME, |
| R.string.japanese_pos_for_dictionary_export_organization_name); |
| map.put(PosType.PLACE_NAME, R.string.japanese_pos_for_dictionary_export_place_name); |
| map.put(PosType.SA_IRREGULAR_CONJUGATION_NOUN, |
| R.string.japanese_pos_for_dictionary_export_sa_irregular_conjugation_noun); |
| map.put(PosType.ADJECTIVE_VERBAL_NOUN, |
| R.string.japanese_pos_for_dictionary_export_adjective_verbal_noun); |
| map.put(PosType.NUMBER, R.string.japanese_pos_for_dictionary_export_number); |
| map.put(PosType.ALPHABET, R.string.japanese_pos_for_dictionary_export_alphabet); |
| map.put(PosType.SYMBOL, R.string.japanese_pos_for_dictionary_export_symbol); |
| map.put(PosType.EMOTICON, R.string.japanese_pos_for_dictionary_export_emoticon); |
| map.put(PosType.ADVERB, R.string.japanese_pos_for_dictionary_export_adverb); |
| map.put(PosType.PRENOUN_ADJECTIVAL, |
| R.string.japanese_pos_for_dictionary_export_prenoun_adjectival); |
| map.put(PosType.CONJUNCTION, R.string.japanese_pos_for_dictionary_export_conjunction); |
| map.put(PosType.INTERJECTION, R.string.japanese_pos_for_dictionary_export_interjection); |
| map.put(PosType.PREFIX, R.string.japanese_pos_for_dictionary_export_prefix); |
| map.put(PosType.COUNTER_SUFFIX, R.string.japanese_pos_for_dictionary_export_counter_suffix); |
| map.put(PosType.GENERIC_SUFFIX, R.string.japanese_pos_for_dictionary_export_generic_suffix); |
| map.put(PosType.PERSON_NAME_SUFFIX, |
| R.string.japanese_pos_for_dictionary_export_person_name_suffix); |
| map.put(PosType.PLACE_NAME_SUFFIX, |
| R.string.japanese_pos_for_dictionary_export_place_name_suffix); |
| map.put(PosType.WA_GROUP1_VERB, R.string.japanese_pos_for_dictionary_export_wa_group1_verb); |
| map.put(PosType.KA_GROUP1_VERB, R.string.japanese_pos_for_dictionary_export_ka_group1_verb); |
| map.put(PosType.SA_GROUP1_VERB, R.string.japanese_pos_for_dictionary_export_sa_group1_verb); |
| map.put(PosType.TA_GROUP1_VERB, R.string.japanese_pos_for_dictionary_export_ta_group1_verb); |
| map.put(PosType.NA_GROUP1_VERB, R.string.japanese_pos_for_dictionary_export_na_group1_verb); |
| map.put(PosType.MA_GROUP1_VERB, R.string.japanese_pos_for_dictionary_export_ma_group1_verb); |
| map.put(PosType.RA_GROUP1_VERB, R.string.japanese_pos_for_dictionary_export_ra_group1_verb); |
| map.put(PosType.GA_GROUP1_VERB, R.string.japanese_pos_for_dictionary_export_ga_group1_verb); |
| map.put(PosType.BA_GROUP1_VERB, R.string.japanese_pos_for_dictionary_export_ba_group1_verb); |
| map.put(PosType.HA_GROUP1_VERB, R.string.japanese_pos_for_dictionary_export_ha_group1_verb); |
| map.put(PosType.GROUP2_VERB, R.string.japanese_pos_for_dictionary_export_group2_verb); |
| map.put(PosType.KURU_GROUP3_VERB, R.string.japanese_pos_for_dictionary_export_kuru_group3_verb); |
| map.put(PosType.SURU_GROUP3_VERB, R.string.japanese_pos_for_dictionary_export_suru_group3_verb); |
| map.put(PosType.ZURU_GROUP3_VERB, R.string.japanese_pos_for_dictionary_export_zuru_group3_verb); |
| map.put(PosType.RU_GROUP3_VERB, R.string.japanese_pos_for_dictionary_export_ru_group3_verb); |
| map.put(PosType.ADJECTIVE, |
| R.string.japanese_pos_for_dictionary_export_adjective); |
| map.put(PosType.SENTENCE_ENDING_PARTICLE, |
| R.string.japanese_pos_for_dictionary_export_sentence_ending_particle); |
| map.put(PosType.PUNCTUATION, R.string.japanese_pos_for_dictionary_export_punctuation); |
| map.put(PosType.FREE_STANDING_WORD, |
| R.string.japanese_pos_for_dictionary_export_free_standing_word); |
| map.put(PosType.SUPPRESSION_WORD, R.string.japanese_pos_for_dictionary_export_suppression_word); |
| |
| if (map.size() != PosType.values().length) { |
| // There seems something unknown POS. |
| throw new AssertionError(); |
| } |
| POS_RESOURCE_MAP_FOR_DICTIONARY_EXPORT = Collections.unmodifiableMap(map); |
| } |
| |
| /** |
| * List of Japanese encodings to convert text for data importing. |
| * These encodings are tries in the order, in other words, UTF-8 is the most high-prioritized |
| * encoding. |
| */ |
| private static final String[] JAPANESE_ENCODING_LIST = { |
| "UTF-8", "EUC-JP", "ISO-2022-JP", "Shift_JIS", "UTF-16", |
| }; |
| |
| private UserDictionaryUtil() { |
| } |
| |
| /** |
| * Returns the text content of the view with the given resourceId. |
| */ |
| private static String getText(View view, int resourceId) { |
| view = view.getRootView(); |
| TextView textView = TextView.class.cast(view.findViewById(resourceId)); |
| return textView.getText().toString(); |
| } |
| |
| /** |
| * Returns the PosType of the view with the given resourceId. |
| */ |
| private static PosType getPos(View view, int resourceId) { |
| view = view.getRootView(); |
| Spinner spinner = Spinner.class.cast(view.findViewById(resourceId)); |
| return PosItem.class.cast(spinner.getSelectedItem()).posType; |
| } |
| |
| /** |
| * Returns string resource id for the given {@code pos}. |
| */ |
| static int getPosStringResourceId(PosType pos) { |
| return POS_RESOURCE_MAP.get(Preconditions.checkNotNull(pos)); |
| } |
| |
| /** |
| * Returns string resource id for the given {@code pos} for dictionary export. |
| */ |
| static int getPosStringResourceIdForDictionaryExport(PosType pos) { |
| return POS_RESOURCE_MAP_FOR_DICTIONARY_EXPORT.get(Preconditions.checkNotNull(pos)); |
| } |
| |
| /** |
| * Returns the instance for Word Register Dialog. |
| */ |
| static WordRegisterDialog createWordRegisterDialog( |
| Context context, int titleResourceId, WordRegisterDialogListener listener, |
| OnDismissListener dismissListener, ToastManager toastManager) { |
| return new WordRegisterDialog( |
| context, titleResourceId, listener, dismissListener, toastManager); |
| } |
| |
| /** |
| * Returns a new instance for Dictionary Name Dialog. |
| */ |
| static DictionaryNameDialog createDictionaryNameDialog( |
| Context context, int titleResourceId, DictionaryNameDialogListener listener, |
| OnDismissListener dismissListener, ToastManager toastManager) { |
| return new DictionaryNameDialog( |
| context, titleResourceId, listener, dismissListener, toastManager); |
| } |
| |
| /** |
| * Returns a new instance for a dialog to select a file in a zipfile. |
| */ |
| static Dialog createZipFileSelectionDialog( |
| Context context, int titleResourceId, |
| DialogInterface.OnClickListener positiveButtonListener, |
| DialogInterface.OnClickListener negativeButtonListener, |
| DialogInterface.OnCancelListener cancelListener, |
| OnDismissListener dismissListener) { |
| return createSimpleSpinnerDialog( |
| context, titleResourceId, positiveButtonListener, negativeButtonListener, |
| cancelListener, dismissListener); |
| } |
| |
| /** |
| * Returns a new instance for a dialog to select import destination. |
| */ |
| static Dialog createImportDictionarySelectionDialog( |
| Context context, int titleResourceId, |
| DialogInterface.OnClickListener positiveButtonListener, |
| DialogInterface.OnClickListener negativeButtonListener, |
| DialogInterface.OnCancelListener cancelListener, |
| OnDismissListener dismissListener) { |
| return createSimpleSpinnerDialog( |
| context, titleResourceId, positiveButtonListener, negativeButtonListener, |
| cancelListener, dismissListener); |
| } |
| |
| private static Dialog createSimpleSpinnerDialog( |
| Context context, int titleResourceId, |
| DialogInterface.OnClickListener positiveButtonListener, |
| DialogInterface.OnClickListener negativeButtonListener, |
| DialogInterface.OnCancelListener cancelListener, |
| OnDismissListener dismissListener) { |
| View view = LayoutInflater.from(context).inflate( |
| R.layout.user_dictionary_tool_simple_spinner_dialog_view, null); |
| AlertDialog dialog = new AlertDialog.Builder(context) |
| .setTitle(titleResourceId) |
| .setView(view) |
| .setPositiveButton(android.R.string.yes, positiveButtonListener) |
| .setNegativeButton(android.R.string.cancel, negativeButtonListener) |
| .setOnCancelListener(cancelListener) |
| .setCancelable(true) |
| .create(); |
| dialog.setOnDismissListener(dismissListener); |
| return dialog; |
| } |
| |
| /** |
| * Sets the parameter to show IME when the given dialog is shown. |
| */ |
| static void showInputMethod(Dialog dialog) { |
| dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); |
| } |
| |
| /** |
| * The default size of dialog looks not good on some devices, such as tablets. |
| * So, modify the dialog size to make the look better. |
| * |
| * The current strategy is: |
| * - if the display is small, we tries to fill the display width by the dialog. |
| * - otherwise, set w480dip to the window. |
| */ |
| static void setDialogWindowSize(Dialog dialog) { |
| DisplayMetrics metrics = dialog.getContext().getResources().getDisplayMetrics(); |
| LayoutParams params = dialog.getWindow().getAttributes(); |
| float dialogWidthThreshold = DIALOG_WIDTH_THRESHOLD * metrics.scaledDensity; |
| if (metrics.widthPixels > dialogWidthThreshold) { |
| params.width = (int) dialogWidthThreshold; |
| } else { |
| params.width = LayoutParams.MATCH_PARENT; |
| } |
| if (dialog instanceof UserDictionaryBaseDialog) { |
| // If the ScrollView hack is used for the dialog to make it scrollable, |
| // it is necessary to set its height parameter MATCH_PARENT as a part of the hack. |
| params.height = LayoutParams.MATCH_PARENT; |
| } |
| dialog.getWindow().setAttributes(params); |
| } |
| |
| /** |
| * Returns the {@code String} instance with detecting the Japanese encoding. |
| * @throws UnsupportedEncodingException if it fails to detect the encoding. |
| */ |
| static String toStringWithEncodingDetection(ByteBuffer buffer) |
| throws UnsupportedEncodingException { |
| for (String encoding : JAPANESE_ENCODING_LIST) { |
| buffer.position(0); |
| try { |
| Charset charset = Charset.forName(encoding); |
| CharBuffer result = charset.newDecoder() |
| .onMalformedInput(CodingErrorAction.REPORT) |
| .onUnmappableCharacter(CodingErrorAction.REPORT) |
| .decode(buffer); |
| String str = result.toString(); |
| if (str.length() > 0 && str.charAt(0) == 0xFEFF) { |
| // Remove leading BOM if necessary. |
| str = str.substring(1); |
| } |
| return str; |
| } catch (Exception e) { |
| // Ignore exceptions, and retry next encoding. |
| } |
| } |
| |
| throw new UnsupportedEncodingException("Failed to detect encoding"); |
| } |
| |
| /** |
| * Reads the text file with detecting the file encoding. |
| */ |
| static String readFromFile(String path) throws IOException { |
| RandomAccessFile file = new RandomAccessFile(path, "r"); |
| boolean succeeded = false; |
| try { |
| String result = readFromFileInternal(file); |
| succeeded = true; |
| return result; |
| } finally { |
| MozcUtil.close(file, !succeeded); |
| } |
| } |
| |
| private static String readFromFileInternal(RandomAccessFile file) throws IOException { |
| FileChannel channel = file.getChannel(); |
| boolean succeeded = false; |
| try { |
| String result = toStringWithEncodingDetection( |
| channel.map(MapMode.READ_ONLY, 0, channel.size())); |
| succeeded = true; |
| return result; |
| } finally { |
| MozcUtil.close(channel, !succeeded); |
| } |
| } |
| |
| /** |
| * Returns import source uri based on the Intent. |
| */ |
| static Uri getImportUri(Intent intent) { |
| String action = intent.getAction(); |
| if (Intent.ACTION_SEND.equals(action)) { |
| return intent.getParcelableExtra(Intent.EXTRA_STREAM); |
| } |
| if (Intent.ACTION_VIEW.equals(action)) { |
| return intent.getData(); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns a name for a new dictionary based on import URI. |
| */ |
| static String generateDictionaryNameByUri(Uri importUri, List<String> dictionaryNameList) { |
| String name = importUri.getLastPathSegment(); |
| |
| // Strip extension |
| { |
| int index = name.lastIndexOf('.'); |
| if (index > 0) { |
| // Keep file path beginning with '.'. |
| name = name.substring(0, index); |
| } |
| } |
| |
| if (!dictionaryNameList.contains(name)) { |
| // No-dupped dictionary name. |
| return name; |
| } |
| |
| // The names extracted from uri is dupped. So look for alternative names by adding |
| // number suffix, such as "pathname (1)". |
| int suffix = 1; |
| while (true) { |
| String candidate = name + " (" + suffix + ")"; |
| if (!dictionaryNameList.contains(candidate)) { |
| return candidate; |
| } |
| // The limit of the number of dictionaries is much smaller than Integer.MAX_VALUE, |
| // so this while loop should stop eventually. |
| ++suffix; |
| } |
| } |
| } |