blob: 422b610072bf309c9a9c6bd89e2f1e9a1d4bf469 [file] [log] [blame]
// 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.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.EditText;
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,
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);
}
@Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
// 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,
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));
}
}, 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,
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));
}
}, 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();
}
}
/** 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,
ToastManager toastManager) {
return new WordRegisterDialog(context, titleResourceId, listener, toastManager);
}
/**
* Returns a new instance for Dictionary Name Dialog.
*/
static DictionaryNameDialog createDictionaryNameDialog(
Context context, int titleResourceId, DictionaryNameDialogListener listener,
ToastManager toastManager) {
return new DictionaryNameDialog(context, titleResourceId, listener, 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) {
return createSimpleSpinnerDialog(
context, titleResourceId, positiveButtonListener, negativeButtonListener,
cancelListener);
}
/**
* 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) {
return createSimpleSpinnerDialog(
context, titleResourceId, positiveButtonListener, negativeButtonListener, cancelListener);
}
private static Dialog createSimpleSpinnerDialog(
Context context, int titleResourceId,
DialogInterface.OnClickListener positiveButtonListener,
DialogInterface.OnClickListener negativeButtonListener,
DialogInterface.OnCancelListener cancelListener) {
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.ok, positiveButtonListener)
.setNegativeButton(android.R.string.cancel, negativeButtonListener)
.setOnCancelListener(cancelListener)
.setCancelable(true)
.create();
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);
}
/**
* 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;
}
}
}