character mapping in edittext android - android

I want to make my edittext like when I write character "g" it's related mapping custom character should written like here in Hindi it's "เคœเฅ€"
I think there should be character mapping but having no knowledge can anybody help me
how to do that
Other app https://play.google.com/store/apps/details?id=nichetech.hindi.editor is also doing same like this way, there is option available offline and online
Online is doing with help of google translator but if I choose Offline then writing happen like this way
Here you can see that Keyboard is English, but writing is done in Hindi language
Thanks
Is there way that I write in English and it's related mapping character will written in EditText of my application only.
Does anybody done like this way then please help me, how to do that

To accomplish what you're after, I would create a HashMap of chars that map to other chars. If some specific char is not mapped just print it out. Here's an example I've put up:
final HashMap<Character, Character> charMap = new HashMap<Character, Character>();
charMap.put('q', '1');
charMap.put('w', '2');
charMap.put('e', '3');
charMap.put('r', '4');
charMap.put('t', '5');
charMap.put('y', '6');
final EditText editText = (EditText) findViewById(R.id.editText);
editText.addTextChangedListener(new TextWatcher() {
boolean replaced;
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
Log.e("TAG", start + " " + before + " " + count);
// Check in lower case
String oldStr = s.toString().toLowerCase();
StringBuilder newStr = new StringBuilder(s);
// Loop through changed chars
for (int i = 0; i < count; i++) {
// Replace if a substitution is avaiable
Character replacement = charMap.get(oldStr.charAt(start + i));
if (replacement != null) {
replaced = true;
newStr.setCharAt(start + i, replacement);
}
}
if (replaced) {
replaced = false;
editText.setText(newStr);
// Move cursor after the new chars
editText.setSelection(start + count);
}
}
#Override
public void afterTextChanged(Editable s) {
}
});
Pros:
Ignores case when looking for a replacement. (q = Q = 1)
Replaces immediately single and multiple chars
Doesn't loop the whole string
Can replace in the middle of another string
Cons:
You have to have a HashMap entry for every character you want replaced
...
As a side-note I'd like to name a few limitations that your given app's "online version" has:
The converting is done only when a space, new line or punctuation mark is entered.
You cannot add letters to already converted words.
The apps "offline" version also has a minor bug:
It doesn't convert words that are copied in or written with Swipe

Key character map files (.kcm files) are responsible for mapping combinations of Android key codes with modifiers to Unicode characters.Device-specific key layout files are required for all internal (built-in) input devices that have keys, if only to tell the system that the device is special purpose only (not a full keyboard).
Device-specific key layout files are optional for external keyboards, and often aren't needed at all. The system provides a generic key character map that is suitable for many external keyboards.
If no device-specific key layout file is available, then the system will choose a default instead.
Key character map files are located by USB vendor, product (and optionally version) id or by input device name.
Key character map files are located by USB vendor, product (and optionally version) id or by input device name.
Suppose the user pressed A and SHIFT together. The system first locates the set of properties and behaviors associated with KEYCODE_A.
key A {
label: 'A'
base: 'a'
shift, capslock: 'A'
ctrl, alt, meta: none
}
ey ESCAPE {
base: fallback BACK
alt, meta: fallback HOME
ctrl: fallback MENU
}
key A {
label: 'A'
number: '2'
base: 'a'
shift, capslock: 'A'
alt: '#'
shift+alt, capslock+alt: none
}
key SPACE {
label: ' '
number: ' '
base: ' '
shift: ' '
alt: '\uef01'
shift+alt: '\uef01'
}

I guess you are better off looking at creating your own *.ttf file with all character mappings. You can just set it on your EditText (or whatever text widget you are using). TTF fonts are welcome in assets/ directory (or any directory you can read from). You can create Typeface object with:
Typeface.createFromAsset(AssetManager mgr, String path)
and setting it with:
textView.setTypeface(mTypeface);

public void MapCharacter() {
this.singleChar.put((Object)"a", (Object)"\u0905");
this.singleChar.put((Object)"b", (Object)"\u092c");
this.singleChar.put((Object)"c", (Object)"\u091a");
this.singleChar.put((Object)"d", (Object)"\u0926");
this.singleChar.put((Object)"e", (Object)"\u090f");
this.singleChar.put((Object)"f", (Object)"\u092b");
this.singleChar.put((Object)"g", (Object)"\u0917");
this.singleChar.put((Object)"h", (Object)"\u0939");
this.singleChar.put((Object)"i", (Object)"\u0907");
this.singleChar.put((Object)"j", (Object)"\u091c");
this.singleChar.put((Object)"k", (Object)"\u0915");
this.singleChar.put((Object)"l", (Object)"\u0932");
this.singleChar.put((Object)"m", (Object)"\u092e");
this.singleChar.put((Object)"n", (Object)"\u0928");
this.singleChar.put((Object)"o", (Object)"\u0913");
this.singleChar.put((Object)"p", (Object)"\u092a");
this.singleChar.put((Object)"q", (Object)"");
this.singleChar.put((Object)"r", (Object)"\u0930");
this.singleChar.put((Object)"s", (Object)"\u0938");
this.singleChar.put((Object)"t", (Object)"\u0924");
this.singleChar.put((Object)"u", (Object)"\u0909");
this.singleChar.put((Object)"v", (Object)"\u0935");
this.singleChar.put((Object)"w", (Object)"\u0935");
this.singleChar.put((Object)"x", (Object)"\u0915\u094d\u0937");
this.singleChar.put((Object)"y", (Object)"\u092f");
this.singleChar.put((Object)"z", (Object)"\u091d");
this.singleChar.put((Object)"A", (Object)"\u0906");
this.singleChar.put((Object)"B", (Object)"\u092c");
this.singleChar.put((Object)"C", (Object)"\u091a");
this.singleChar.put((Object)"D", (Object)"\u0921");
this.singleChar.put((Object)"E", (Object)"\u090d");
this.singleChar.put((Object)"F", (Object)"\u092b");
this.singleChar.put((Object)"G", (Object)"\u0917");
this.singleChar.put((Object)"H", (Object)"\u0903");
this.singleChar.put((Object)"I", (Object)"\u0908");
this.singleChar.put((Object)"J", (Object)"\u091c");
this.singleChar.put((Object)"K", (Object)"\u0915");
this.singleChar.put((Object)"L", (Object)"\u0933");
this.singleChar.put((Object)"M", (Object)"\u0902");
this.singleChar.put((Object)"N", (Object)"\u0923");
this.singleChar.put((Object)"O", (Object)"\u0911");
this.singleChar.put((Object)"P", (Object)"\u092a");
this.singleChar.put((Object)"Q", (Object)"");
this.singleChar.put((Object)"R", (Object)"\u0930");
this.singleChar.put((Object)"S", (Object)"\u0936");
this.singleChar.put((Object)"T", (Object)"\u091f");
this.singleChar.put((Object)"U", (Object)"\u090a");
this.singleChar.put((Object)"V", (Object)"\u0935");
this.singleChar.put((Object)"W", (Object)"\u0935");
this.singleChar.put((Object)"X", (Object)"\u0915\u094d\u0937");
this.singleChar.put((Object)"Y", (Object)"\u092f");
this.singleChar.put((Object)"Z", (Object)"\u091d");
this.singleChar.put((Object)"1", (Object)"\u0967");
this.singleChar.put((Object)"2", (Object)"\u0968");
this.singleChar.put((Object)"3", (Object)"\u0969");
this.singleChar.put((Object)"4", (Object)"\u096a");
this.singleChar.put((Object)"5", (Object)"\u096b");
this.singleChar.put((Object)"6", (Object)"\u096c");
this.singleChar.put((Object)"7", (Object)"\u096d");
this.singleChar.put((Object)"8", (Object)"\u096e");
this.singleChar.put((Object)"9", (Object)"\u096f");
this.singleChar.put((Object)"0", (Object)"\u0966");
this.singleChar.put((Object)"#", (Object)"\u0953");
this.singleChar.put((Object)"$", (Object)" \u0951");
this.singleChar.put((Object)"^", (Object)"\u094d");
this.singleChar.put((Object)":", (Object)"\u0903");
this.delimtrChar.put((Object)" ", (Object)" ");
this.delimtrChar.put((Object)"!", (Object)"!");
this.delimtrChar.put((Object)"#", (Object)"\u0970");
this.delimtrChar.put((Object)"%", (Object)"%");
this.delimtrChar.put((Object)"&", (Object)"\u093d");
this.delimtrChar.put((Object)"(", (Object)"(");
this.delimtrChar.put((Object)")", (Object)")");
this.delimtrChar.put((Object)"~", (Object)"~");
this.delimtrChar.put((Object)"`", (Object)"`");
this.delimtrChar.put((Object)"_", (Object)"_");
this.delimtrChar.put((Object)"=", (Object)"=");
this.delimtrChar.put((Object)"{", (Object)"{");
this.delimtrChar.put((Object)"}", (Object)"}");
this.delimtrChar.put((Object)"|", (Object)"\u0964");
this.delimtrChar.put((Object)"\"", (Object)"\"");
this.delimtrChar.put((Object)"<", (Object)"<");
this.delimtrChar.put((Object)">", (Object)">");
this.delimtrChar.put((Object)"?", (Object)"?");
this.delimtrChar.put((Object)"+", (Object)"+");
this.delimtrChar.put((Object)"-", (Object)"-");
this.delimtrChar.put((Object)"[", (Object)"[");
this.delimtrChar.put((Object)"]", (Object)"]");
this.delimtrChar.put((Object)"\\", (Object)"\\");
this.delimtrChar.put((Object)";", (Object)";");
this.delimtrChar.put((Object)"'", (Object)"'");
this.delimtrChar.put((Object)",", (Object)",");
this.delimtrChar.put((Object)".", (Object)".");
this.delimtrChar.put((Object)"/", (Object)"/");
this.doubleChar.put((Object)"aa", (Object)"\u0906");
this.doubleChar.put((Object)"ai", (Object)"\u0910");
this.doubleChar.put((Object)"au", (Object)"\u0914");
this.doubleChar.put((Object)"ou", (Object)"\u0914");
this.doubleChar.put((Object)"ee", (Object)"\u0908");
this.doubleChar.put((Object)"oo", (Object)"\u090a");
this.doubleChar.put((Object)"aM", (Object)"\u0905\u0902");
this.doubleChar.put((Object)"aM~", (Object)"\u0905\u0901");
this.doubleChar.put((Object)"aH", (Object)"\u0905\u0903");
this.doubleChar.put((Object)"a:", (Object)"\u0905\u0903");
this.doubleChar.put((Object)"NG", (Object)"\u0919");
this.doubleChar.put((Object)"OM", (Object)"\u0950");
this.doubleChar.put((Object)"+~", (Object)"\u5350");
this.doubleChar.put((Object)"Rs", (Object)"\u20b9");
this.doubleChar.put((Object)"||", (Object)"\u0965");
this.doubleChar.put((Object)"NY", (Object)"\u091e");
this.doubleChar.put((Object)"Gy", (Object)"\u091c\u094d\u091e");
this.doubleChar.put((Object)"kh", (Object)"\u0916");
this.doubleChar.put((Object)"gh", (Object)"\u0918");
this.doubleChar.put((Object)"Ch", (Object)"\u091b");
this.doubleChar.put((Object)"chh", (Object)"\u091b");
this.doubleChar.put((Object)"ch", (Object)"\u091a");
this.doubleChar.put((Object)"th", (Object)"\u0925");
this.doubleChar.put((Object)"Th", (Object)"\u0920");
this.doubleChar.put((Object)"dh", (Object)"\u0927");
this.doubleChar.put((Object)"Dh", (Object)"\u0922");
this.doubleChar.put((Object)"jh", (Object)"\u091d");
this.doubleChar.put((Object)"ph", (Object)"\u092b");
this.doubleChar.put((Object)"bh", (Object)"\u092d");
this.doubleChar.put((Object)"sh", (Object)"\u0936");
this.doubleChar.put((Object)"Sh", (Object)"\u0937");
this.doubleChar.put((Object)"kSh", (Object)"\u0915\u094d\u0937");
this.doubleChar.put((Object)"Ri", (Object)"\u090b");
this.doubleChar.put((Object)"RI", (Object)"\u0960");
this.doubleChar.put((Object)"Li~", (Object)"\u090c");
this.doubleChar.put((Object)"LI~", (Object)"\u0961");
this.doubleChar.put((Object)"##", (Object)"\u0971");
this.doubleChar.put((Object)"$$", (Object)"\u0952");
this.matraChar.put((Object)"a", (Object)"\u093e");
this.matraChar.put((Object)"A", (Object)"\u093e");
this.matraChar.put((Object)"i", (Object)"\u093f");
this.matraChar.put((Object)"I", (Object)"\u0940");
this.matraChar.put((Object)"u", (Object)"\u0941");
this.matraChar.put((Object)"U", (Object)"\u0942");
this.matraChar.put((Object)"e", (Object)"\u0947");
this.matraChar.put((Object)"E", (Object)"\u0945");
this.matraChar.put((Object)"o", (Object)"\u094b");
this.matraChar.put((Object)"O", (Object)"\u0949");
this.matraChar.put((Object)"ai", (Object)"\u0948");
this.matraChar.put((Object)"au", (Object)"\u094c");
this.matraChar.put((Object)"ou", (Object)"\u094c");
this.matraChar.put((Object)"aa", (Object)"\u093e");
this.matraChar.put((Object)"oo", (Object)"\u0942");
this.matraChar.put((Object)"ee", (Object)"\u0940");
this.matraChar.put((Object)"*", (Object)"\u093c");
this.matraChar.put((Object)"M~", (Object)"\u0901");
this.matraChar.put((Object)"r", (Object)"\u094d\u0930");
this.matraChar.put((Object)"R", (Object)"\u0930\u094d");
this.matraChar.put((Object)"Ri", (Object)"\u0943");
this.matraChar.put((Object)"RI", (Object)"\u0944");
this.matraChar.put((Object)"Li~", (Object)"\u0962");
this.matraChar.put((Object)"LI~", (Object)"\u0963");
}

Related

how to remove special characters when i paste +1-xxx-xxx-xxxx from another source in android edit text with 10 digits length in android

copy from another source is : +1-541-xxx-3010
when i paste in my edit text i need to get the result as follow :
541xxx3010
need to remove special characters and also need to remove +1 (country code)
i need to display only 10 digits actual number after removing special chars
#Override
public void onTextChanged(CharSequence s, int start, int before, int count){
String edit = s.toString();
System.out.println("##"+edit);
edit = edit.replaceAll("[^0-9]", "");
String result = edit.replaceAll("[|?*<\":>+\\[\\]/'-]","");
System.out.println(result);
}
You can try SubString to get only 10 digit from your string.
For E.g. Your String is +1-541-xxx-3010. Now as you want you need to remove the + and country code.
String phone = "+1-541-xxx-3010"
result = phone.substring(Math.max(phone.length() - 12, 0)).replaceAll("-", ""));
From the above code it returns the result like this.
Result: 541xxx3010
NOTE : Before apply this code you need to check if your string is not
empty or its size is greater than or equal 12.
OR
If you don't want to use + and - then why don't you restrict the character using android:digits just apply below property in edittext.So it will not allow to enter the character apart from 0-9.
android:digits="0123456789"
First you need to remove country code from the string using substring() method,
For remove special character in your case it is "-" you can refer below code snippet
String result = inputString.replaceAll("[-]","");

Android - How to filter emoji (emoticons) from a string?

I'm working on an Android app, and I do not want people to use emoji in the input.
How can I remove emoji characters from a string?
Emojis can be found in the following ranges (source) :
U+2190 to U+21FF
U+2600 to U+26FF
U+2700 to U+27BF
U+3000 to U+303F
U+1F300 to U+1F64F
U+1F680 to U+1F6FF
You can use this line in your script to filter them all at once:
text.replace("/[\u2190-\u21FF]|[\u2600-\u26FF]|[\u2700-\u27BF]|[\u3000-\u303F]|[\u1F300-\u1F64F]|[\u1F680-\u1F6FF]/g", "");
Latest emoji data can be found here:
http://unicode.org/Public/emoji/
There is a folder named with emoji version.
As app developers a good idea is to use latest version available.
When You look inside a folder, You'll see text files in it.
You should check emoji-data.txt. It contains all standard emoji codes.
There are a lot of small symbol code ranges for emoji.
Best support will be to check all these in Your app.
Some people ask why there are 5 digit codes when we can only specify 4 after \u.
Well these are codes made from surrogate pairs. Usually 2 symbols are used to encode one emoji.
For example, we have a string.
String s = ...;
UTF-16 representation
byte[] utf16 = s.getBytes("UTF-16BE");
Iterate over UTF-16
for(int i = 0; i < utf16.length; i += 2) {
Get one char
char c = (char)((char)(utf16[i] & 0xff) << 8 | (char)(utf16[i + 1] & 0xff));
Now check for surrogate pairs. Emoji are located on the first plane, so check first part of pair in range 0xd800..0xd83f.
if(c >= 0xd800 && c <= 0xd83f) {
high = c;
continue;
}
For second part of surrogate pair range is 0xdc00..0xdfff. And we can now convert a pair to one 5 digit code.
else if(c >= 0xdc00 && c <= 0xdfff) {
low = c;
long unicode = (((long)high - 0xd800) * 0x400) + ((long)low - 0xdc00) + 0x10000;
}
All other symbols are not pairs so process them as is.
else {
long unicode = c;
}
Now use data from emoji-data.txt to check if it's emoji.
If it is, then skip it. If not then copy bytes to output byte array.
Finally byte array is converted to String by
String out = new String(outarray, Charset.forName("UTF-16BE"));
For those using Kotlin, Char.isSurrogate can help as well. Find and remove the indexes that are true from that.
Here is what I use to remove emojis. Note: This only works on API 24 and forwards
public String remove_Emojis_For_Devices_API_24_Onwards(String name)
{
// we will store all the non emoji characters in this array list
ArrayList<Character> nonEmoji = new ArrayList<>();
// this is where we will store the reasembled name
String newName = "";
//Character.UnicodeScript.of () was not added till API 24 so this is a 24 up solution
if (Build.VERSION.SDK_INT > 23) {
/* we are going to cycle through the word checking each character
to find its unicode script to compare it against known alphabets*/
for (int i = 0; i < name.length(); i++) {
// currently emojis don't have a devoted unicode script so they return UNKNOWN
if (!(Character.UnicodeScript.of(name.charAt(i)) + "").equals("UNKNOWN")) {
nonEmoji.add(name.charAt(i));//its not an emoji so we add it
}
}
// we then cycle through rebuilding the string
for (int i = 0; i < nonEmoji.size(); i++) {
newName += nonEmoji.get(i);
}
}
return newName;
}
so if we pass in a string:
remove_Emojis_For_Devices_API_24_Onwards("๐Ÿ˜Š test ๐Ÿ˜Š Indic:เคข Japanese:ใช ๐Ÿ˜Š Korean:ใ…‚");
it returns: test Indic:เคข Japanese:ใช Korean:ใ…‚
Emoji placement or count doesn't matter

Format Android EditText to specific pattern

I need the user to enter text in an EditText according to this specfic pattern:
123.456-7890-123.456
The user can input any number of integers, so they could as well enter 123.456-7
I do not want the user to enter . or - just the numbers, like an input mask.
Also the numeric keyboard should only show.
I've searched StackOverflow extensively and have seen examples that use InputFilter, ChangedListener, TextWatcher but have not found anything simlar to what I'm trying to do. I've tried in various implementations of what I've found, but I'm inexperienced in using these so I may have overlooked something.
Any suggestions would be welcome.
You're going to have to use a TextWatcher and a regular expression pattern matcher to accomplish what you're trying to do.
This answer should be helpful: Android AutoCompleteTextView with Regular Expression?
You can create your own class that implements InputFilter. Then you would apply it as follows:
MyInputFilter filter = new MyInputFilter(...);
editText.setFilters(new InputFilter[]{filter});
Refer to the docs for how InputFilter is intended to work, then refer to the source code for some of the InputFilters used in Android for some ideas how to implement them.
After many failed attempts to implement InputFilter or Regular Expressions I opted for something a little more straight forward:
public void onTextChanged(CharSequence s, int start, int before, int count) {
String a = "";
String str = id.getText().toString();
String replaced = str.replaceAll(Pattern.quote("."),"");
replaced = replaced.replaceAll(Pattern.quote("-"),"");
char[] id_char = replaced.toCharArray();
int id_len = replaced.length();
for(int i = 0; i < id_len; i++) {
if(i == 2 || i == 12) {
a += id_char[i] + ".";
}
else if (i == 5 || i == 9) {
a += id_char[i] + "-";
}
else a += id_char[i];
}
id.removeTextChangedListener(this);
id.setText(a);
if(before > 0) id.setSelection(start);
else id.setSelection(a.length());
id.addTextChangedListener(this);
}
I don't know if this is the best approach but it does work. One problem I still haven't solved is how to handle cursor placement after the user deletes or inserts a number. If the user inserts the cursor somewhere in the EditText and enters a new number the cursor jumps to the end of the EditText. I would like the cursor to stay where it is at. Another problem if the user inserts the cursor within the EditText number and backspaces to delete a number, then the first key entry doesn't work and on the second key the number is entered. I can only guess this has to do with focus?
Use this: https://github.com/alobov/SimpleMaskWatcher.
Just set your mask for this watcher (###.###-####-###.###). It will add special symbols automatically and wont check your input string for being complete.
But showing the numeric keyboard you must handle by your own using android:inputType="number" tag for your EditText.

Injecting non-English characters in Android

I am programming on a remote control app. One of the tasks is injecting characters. The code I am currently using looks like this:
Instrumentation instr = new Instrumentation();
String str="a";
// basically the same like calling instr.sendStringSync(str);
char[] chars = str.toCharArray();
KeyCharacterMap keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
KeyEvent[] keyEvents = keyCharacterMap.getEvents(chars);
if (keyEvents != null) {
for (KeyEvent kev : keyEvents) {
instr.sendKeySync(kev);
}
}
That works perfectly on English characters (The characters show up in EditText boxes). However, if I am trying to inject e.g. Korean characters, this fails. The function getEvents returns null, even when I have configured Korean language and keyboard.
I know there is another method for injecting strings directly:
KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(), str, 0, 0);
instr.sendKeySync(event);
This is not working either - no characters shown in EditText boxes, and onKeyMultiple() is not called either in my test activity.
This is strange since dispatchKeyEvent() with the same event works in my test activity:
KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(), str, 0, 0);
dispatchKeyEvent(event);
My remote control app needs to inject events no matter to which activity. This is possible using Instrumentation (with android.permission.INJECT_EVENTS and a signature with the platform key).
How can I inject non-English characters using instrumentation?
Is there another way to accomplish this? E.g. Using dispatchKeyEvent (has to work for other activities/apps as well).
I leave the part above as extra info. I have found a solution. It requires to have root, but if you can sign with the application key I guess that's not a problem. What you can do is edit the file Virtual.kcm (/system/usr/keychars/Virtual.kcm), which is the default key character map (kcm). You can add any character you want, and then use the method Instrumentation.sendStringSync(String string), because it will be able to generate KeyEvents from the new kcm.
I had some problems editting the kcm on the phone, so what I did was to copy it on a computer, edit it there and then copy it back to the device.
I hope this helps!
In this link, the following content appears. It seams that the virtual keyboard has a US keyCharacterMap and layout, no matter what you choose on settings. I haven't been able to find a way to solve this.
Language Note
Android does not currently support multilingual keyboards. Moreover, the built-in generic key character map assumes a US English keyboard layout.
OEMs are encouraged to provide custom key character maps for their keyboards if they are designed for other languages.
Future versions of Android may provide better support for multilingual keyboards or user-selectable keyboard layouts.
You can use this method and InputFilter for EditText:
private boolean isLatinSymbolOrDigit(char c)
{
// Allow [a-zA-Z0-9]
if ('0' <= c && c <= '9')
return true;
if ('a' <= c && c <= 'z')
return true;
if ('A' <= c && c <= 'Z')
return true;
return false;
}
InputFilter filter = new InputFilter()
{
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend)
{
for (int i = start; i < end; i++)
{
if (isLatinSymbolOrDigit(source.charAt(i)))
{
}
else
{
//wrong character
return "";
}
}
return null;
}
};
editText.setFilters(new InputFilter[] {filter});

SpannableStringBuffer limited to 9,999 characters?

My app reads in large amounts of data from text files assets and displays them on-screen in a TextView. (The largest is ~450k.) I read the file in, line-by-line into a SpannableStringBuffer (since there is some metadata I remove, such as section names). This approach has worked without complaints in the two years that I've had the app on the market (over 7k active device installs), so I know that the code is reasonably correct.
However, I got a recent report from a user on a LG Lucid (LGE VS840 4G, Android 2.3.6) that the text is truncated. From log entries, my app only got 9,999 characters in the buffer. Is this a known issue with a SpannableStringBuffer? Are there other recommended ways to build a large Spannable buffer? Any suggested workarounds?
Other than keeping a separate expected length that I update each time I append to the SpannableStringBuilder, I don't even have a good way to detect the error, since the append interface returns the object, not an error!
My code that reads in the data is:
currentOffset = 0;
try {
InputStream is = getAssets().open(filename);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
ssb.clear();
jumpOffsets.clear();
ArrayList<String> sectionNamesList = new ArrayList<String>();
sectionOffsets.clear();
int offset = 0;
while (br.ready()) {
String s = br.readLine();
if (s.length() == 0) {
ssb.append("\n");
++offset;
} else if (s.charAt(0) == '\013') {
jumpOffsets.add(offset);
String name = s.substring(1);
if (name.length() > 0) {
sectionNamesList.add(name);
sectionOffsets.add(offset);
if (showSectionNames) {
ssb.append(name);
ssb.append("\n");
offset += name.length() + 1;
}
}
} else {
if (!showNikud) {
// Remove nikud based on Unicode character ranges
// Does not replace combined characters (\ufb20-\ufb4f)
// See
// http://en.wikipedia.org/wiki/Unicode_and_HTML_for_the_Hebrew_alphabet
s = s. replaceAll("[\u05b0-\u05c7]", "");
}
if (!showMeteg) {
// Remove meteg based on Unicode character ranges
// Does not replace combined characters (\ufb20-\ufb4f)
// See
// http://en.wikipedia.org/wiki/Unicode_and_HTML_for_the_Hebrew_alphabet
s = s.replaceAll("\u05bd", "");
}
ssb.append(s);
ssb.append("\n");
offset += s.length() + 1;
}
}
sectionNames = sectionNamesList.toArray(new String[0]);
currentFilename = filename;
Log.v(TAG, "ssb.length()=" + ssb.length() +
", daavenText.getText().length()=" +
daavenText.getText().length() +
", showNikud=" + showNikud +
", showMeteg=" + showMeteg +
", showSectionNames=" + showSectionNames +
", currentFilename=" + currentFilename
);
After looking over the interface, I plan to replace the showNikud and showMeteg cases with InputFilters.
Is this a known issue with a SpannableStringBuffer?
I see nothing in the source code to suggest a hard limit on the size of a SpannableStringBuffer. Given your experiences, my guess is that this is a problem particular to that device, due to a stupid decision by an engineer at the device manufacturer.
Any suggested workarounds?
If you are distributing through the Google Play Store, block this device in your console.
Or, don't use one massive TextView, but instead use several smaller TextView widgets in a ListView (so they can be recycled), perhaps one per paragraph. This should have the added benefit of reducing your memory footprint.
Or, generate HTML and display the content in a WebView.
After writing (and having the user run) a test app, it appears that his device has this arbitrary limit for SpannableStringBuilder, but not StringBuilder or StringBuffer. I tested a quick change to read into a StringBuilder and then create a SpannableString from the result. Unfortunately, that means that I can't create the spans until it is fully read in.
I have to consider using multiple TextView objects in a ListView, as well as using Html.FromHtml to see if that works better for my app's long term plans.

Categories

Resources