Android TV定制输入法

TV输入法原理

Android Latin输入法的基础上进行改写,该输入法不支持中文,处理流程如图:


第一次启动输入法服务时,根据键盘布局文件创建软键盘view,分别是全字母软键盘、数字软键盘以及特殊字符软键盘。在创建软键盘时,把每一个软键盘上的所有按键放到一个HashMap中,使每一个按键都分别与一个String型的数字对应。三个软键盘view对应的HashMap分别是qwertyMapsymbolsMapsymbolsShiftMap

添加坐标类KeyCoordinates,该类的成员属性包括横坐标x和纵坐标y,成员方法包括设置横纵坐标setX()setY(),以及获取横纵坐标getX()getY()

在输入法service类中,添加输入法状态标志位属性isInputMode,默认为falseisInputMode值为true时,软键盘上显示焦点,遥控器上方向键和确认键只用于软键盘上焦点的移动和焦点所在按键字符的输入,不具有系统原有功能,isInputModefalse时,软键盘上不会显示焦点,方向键和确认键具有系统原有功能,不用于对软件盘进行操作。

添加KeyCoordinates属性用来标记当前焦点所在位置,默认横纵坐标均为-1,即(-1,-1)。添加属性numorinum,默认均为-1,其中num用来标记当前焦点所在按键的位置,orinum用来标记上一次焦点所在按键的位置。添加属性CHANGE_INPUTMODE_KEYCODE,该常量用来标记触发输入法状态切换的KeyEvent事件的keyCode值。

在进行输入时,软键盘打开的时候没有焦点。

按下遥控器上的输入法状态切换键后,触发onKeyDown()事件,首先判断软键盘此刻是否处于显示状态,即调用该软键盘viewisShown()方法,如果为false,不对输入法执行任何操作。否则判断出该KeyEvent事件的keyCode等于CHANGE_INPUTMODE_KEYCODE,执行输入法状态切换操作,如果当前isInputModefalse,则将isInputMode设为trueKeyCoordinates设为(4,1),即软键盘中间按键对应的坐标位置,根据KeyCoordinatesxy的值换算成对应的值存放到num中(如(0,1)换算成1。

软键盘不同,进行换算结果也不同,具体由软键盘按键个数和布局决定),再到当前软键盘对应的HashMap中取得num对应的按键,将焦点设到该按键上,并且将该按键的pressed状态设为true,高亮显示。

如果当前isInputModetrue,则将isInputMode设为false,将numorinum的值均设为-1KeyCoordinates设为(-1,-1),焦点所在按键的pressed状态设为false,取消焦点,软键盘上不再显示焦点。

按下遥控器上的左方向键,触发onKeyDown()事件,首先判断软键盘此刻是否处于显示状态,即调用该软键盘viewisShown()方法,如果为false,不对输入法执行任何操作,而是返回给系统进行调用。否则不返回给系统进行调用,只用于对软键盘进行操作,判断出该KeyEvent事件的keyCode等于KeyEvent.KEYCODE_DPAD_LEFT,然后再判断isInputMode的值,如果为false,不对输入法执行任何操作;如果为true,则对KeyCoordinates中的横坐标x进行操作,如果当前焦点所在的按键已经是最左边的按键,不进行任何操作,否则,x的值减1,然后将num的值赋给orinum。

根据KeyCoordinatesxy的值换算成对应的值存放到num中,再到当前软键盘对应的HashMap中取得num对应的按键,将焦点移动到该按键上,设置该按键的pressed状态为true,高亮显示,到当前软键盘对应的HashMap中取得orinum对应的按键(即之前焦点所在的按键),将该按键pressed状态设为false,不再高亮显示。按下遥控器上其他方向键处理类似左方向键。

按下遥控器上的确认键,触发onKeyDown()事件,首先判断软键盘此刻是否处于显示状态,即调用该软键盘viewisShown()方法,如果为false,不对输入法执行任何操作,而是返回给系统进行调用。否则不返回给系统进行调用,只用于对软键盘进行操作,判断出该KeyEvent事件的keyCode等于KeyEvent.KEYCODE_ENTER,然后再判断isInputMode的值,如果为false,不对输入法执行任何操作;如果为true,则调用方法onKey(),输入当前焦点所在的按键。

如果该按键为普通的字符键,则输入该按键代表的字符,如果该按键为shift键,则对软键盘执行shift操作,如果该按键为软键盘切换键(比如用于由全字母软键盘切换到数字软键盘),则将软键盘切换到另外一个软键盘,切换后,KeyCoordinates设为(4,1),即切换之后的软键盘中间按键对应的坐标位置,根据KeyCoordinatesxy的值换算成对应的值存放到num中,再到当前软键盘对应的HashMap中取得num对应的按键,将焦点设到该按键上,并且将该按键的pressed状态设为true,高亮显示。

切换之前的软件盘上不再显示焦点,焦点所在按键的pressed状态设为false,如果该按键为删除键,则删除已输入的字符,如果该按键为确认键,则将执行系统调用的确认操作,按键的pressed状态设为false,软键盘上不再显示焦点,完成本次输入。

按下遥控器上的方向键或确认键,然后在释放的时候,触发onKeyUp()事件,首先判断软键盘此刻是否处于显示状态,即调用该软键盘viewisShown()方法,如果为false,不对输入法执行任何操作,而是返回给系统进行调用。如果为true,则不执行任何操作,也不返回给系统进行调用。当然要想支持中文,也可以修改android自带的google拼音输入法,原理同上。

LatinIME代码定向修改

添加功能

为输入法软键盘键子添加焦点(边框),并支持键盘上的方向键,在按上下左右方向键时,焦点跟着方向键移动;并支持键盘回车按钮,在按下回车键后,将软键盘对应键子内容输入到editor(编辑框)。


代码修改部分

1、在LatinKeyboardView中重写onDraw(Canvas canvas)

//BEGIN: add Keyboard focus
private Keyboard currentKeyboard;
private List<Key> keys = new ArrayList<Key>();
private int lastKeyIndex = 0;
private Key focusedKey;
private Rect rect;
/**
* override onDraw() method to draw rectangle focus round key
* @param canvas
*/
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
currentKeyboard = this.getKeyboard();
keys = currentKeyboard.mKeys;
Paint p = new Paint();
p.setColor(Color.CYAN);
p.setStyle(Style.STROKE);
p.setStrokeWidth(4.0f);
if(lastKeyIndex > keys.size()){
lastKeyIndex = keys.size() - 1;
}
focusedKey = keys.get(lastKeyIndex);
rect = new Rect(focusedKey.mX,focusedKey.mY+2,focusedKey.mX+focusedKey.mWidth,focusedKey.mY+focusedKey.mHeight);
if(this.isFocusFlag()){
canvas.drawRect(rect, p);
}
}

/** provide lastKeyIndex access */
public int getLastKeyIndex() {
return lastKeyIndex;
}


/** set key index */
public void setLastKeyIndex(int index) {
this.lastKeyIndex = index;
}


    /**The keyboard is get the focus*/
private boolean focusFlag = false;


public boolean isFocusFlag() {
return focusFlag;
}


public void setFocusFlag(boolean focusFlag) {
this.focusFlag = focusFlag;
}

2、在LatinIME类中重写onKeyDown(int keyCode, KeyEvent event),在方法中对方向键进行判断,并设计焦点移动过程。

/** Deal with remote control key event
*
* @param keyCode
* @param event
* @return
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
hideFlag = false;
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
if (event.getRepeatCount() == 0) {
if (mSuggestionsView !=null && mSuggestionsView.handleBack()) {
return true;
}
final LatinKeyboardViewkeyboardView = mKeyboardSwitcher
.getKeyboardView();
if (keyboardView != null && keyboardView.handleBack()) {
return true;
}
}
if (inputView != null && inputView.handleBack()) {
return true;
}
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
if (inputView != null && inputView.isShown()) {
setFields();
if (nLastKeyIndex >= nCurKeyboardKeyNums - 1) {
inputView.setLastKeyIndex(0);
} else {
int[] nearestKeyIndices= nCurrentKeyboard.getNearestKeys(
nKeys.get(nLastKeyIndex).mX,
nKeys.get(nLastKeyIndex).mY);
Key lastKey = nKeys.get(nLastKeyIndex);
for (int i = 0;i < nearestKeyIndices.length; i++) {
int index = nearestKeyIndices[i];
if (nLastKeyIndex < index) {
Key nearestKey = nKeys.get(index);
// tell right bound
if ((lastKey.mX+ lastKey.mWidth) > nearestKey.mX
&& (lastKey.mX + lastKey.mWidth) <= (nearestKey.mX + nearestKey.mWidth)) {
inputView.setLastKeyIndex(index);
break;
}
// ensure not bound outof softkeyboard
if (nearestKey.mX + nearestKey.mWidth
+ lastKey.mWidth > nCurrentKeyboard.mOccupiedWidth
&& lastKey.mX >= nearestKey.mX
&& lastKey.mX <(nearestKey.mX + nearestKey.mWidth)) {
Log.d(TAG, "if two " + index);
inputView.setLastKeyIndex(index);
break;
}
if (i == (nearestKeyIndices.length - 1)
&& (lastKey.mY + lastKey.mHeight + nearestKey.mHeight) <= nCurrentKeyboard.mOccupiedHeight) {
Log.d(TAG, "if three " + index);
inputView.setLastKeyIndex(index);
break;
}


}
}// end for loop
}
if (inputView != null && inputView.isShown()) {
inputView.setFocusFlag(true);
inputView.invalidate();
}
return true;
}
break;
case KeyEvent.KEYCODE_DPAD_UP:
if (inputView != null && inputView.isShown()) {
setFields();
if (nLastKeyIndex <= 0){
inputView.setLastKeyIndex(nCurKeyboardKeyNums -1);
} else {
int[] nearestKeyIndices= nCurrentKeyboard.getNearestKeys(
nKeys.get(nLastKeyIndex).mX,
nKeys.get(nLastKeyIndex).mY);
// get current displayed
Key lastKey = nKeys.get(nLastKeyIndex);
for (int i = nearestKeyIndices.length -1; i >= 0; i--){
int index = nearestKeyIndices[i];
if (nLastKeyIndex > index) {
// get the nextkey
Key nearKey = nKeys.get(index);
// BEGIN: laskKey+1 :Because the last key better
// than his last one button x to small 1
if (lastKey.mX + 1 >= nearKey.mX
&& lastKey.mX <nearKey.mX + nearKey.mWidth) {
inputView.setLastKeyIndex(index);
break;
} else if (nearKey.mX -nearKey.mWidth < 0
&& lastKey.mX <= nearKey.mX) {
inputView.setLastKeyIndex(index);
break;
}
}
}// end for loop
}
if (inputView != null && inputView.isShown()) {
inputView.setFocusFlag(true);
inputView.invalidate();
}
return true;
}
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
if (inputView != null && inputView.isShown()) {
setFields();
if (nLastKeyIndex <= 0){
inputView.setLastKeyIndex(nCurKeyboardKeyNums -1);
} else {
nLastKeyIndex--;
inputView.setLastKeyIndex(nLastKeyIndex);
}
inputView.setFocusFlag(true);
inputView.invalidate();
return true;
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (inputView != null && inputView.isShown()) {
setFields();
if (nLastKeyIndex >= nCurKeyboardKeyNums - 1) {
inputView.setLastKeyIndex(0);
} else {
nLastKeyIndex++;
inputView.setLastKeyIndex(nLastKeyIndex);
}
inputView.setFocusFlag(true);
inputView.invalidate();
return true;
}
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
Log.d(TAG, "[onKeyDown]::dpad_center========"+ keyCode);
if (inputView != null && inputView.isShown()) {
setFields();
final Key key = nKeys.get(nLastKeyIndex);
if (key.mOutputText != null) {
onTextInput(key.mOutputText);
}else if(mKeyboardSwitcher.isShiftedOrShiftLocked() && mKeyboardSwitcher.isShiftLocked()){
int curKeyCode = nKeys.get(nLastKeyIndex).mCode;
onCodeInput(curKeyCode, new int[] { curKeyCode },
nKeys.get(nLastKeyIndex).mX,
nKeys.get(nLastKeyIndex).mY);
}else if(mKeyboardSwitcher.isShiftedOrShiftLocked() && null != key.mHintLabel){
onTextInput(key.mHintLabel);
}else{ 
int curKeyCode = nKeys.get(nLastKeyIndex).mCode;
onCodeInput(curKeyCode, new int[] { curKeyCode },
nKeys.get(nLastKeyIndex).mX,
nKeys.get(nLastKeyIndex).mY);
}
return true;
}
break;
case KeyEvent.KEYCODE_DEL:
keyCode = -67;
onCodeInput(keyCode, new int[]{keyCode}, 0, 0);
break;
default:
if ((false == event.isCapsLockOn()) && (false == event.isShiftPressed())
&&((keyCode <= KeyEvent.KEYCODE_Z && keyCode >= KeyEvent.KEYCODE_A)))
{
if (event.getKeyCode() <= KeyEvent.KEYCODE_Z)
{
//Change to char key
keyCode = keyCode + 68;
onCodeInput(keyCode, new int[] { keyCode },
0,
0);
return true;
}
}
else if (keyCode == KeyEvent.KEYCODE_SPACE)
{
//Change to space key
keyCode = 32;
onCodeInput(keyCode, new int[] { keyCode },
0,
0);
return true;
}
//END
break;
}
return super.onKeyDown(keyCode,event);
}

LatinIME修改默认输入法语言


Android系统默认都是选择系统语言作为输入法,比如我们要用中文输入法,就需要切换系统语言为中文,或不勾选系统语言,主动勾选中文,但是我们怎么修改默认的语言输入法呢?

主要要关注3个模块代码:
(1) Setting源码
(2) SettingsProvider源码
(3) LatinIME输入法源码


方法一:修改默认输入法语言为英文和泰文

(1) 修改 packages\inputmethods\LatinIME\java\AndroidManifest.xml,增加
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<receiver android:name="LatinImeReceiver" android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>

(2) 在packages\inputmethods\LatinIME\java\src\com\android\inputmethod\latin目录增加LatinImeReceiver.java文件,源码如下
package com.android.inputmethod.latin;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.provider.Settings;
import android.util.Log;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import android.text.TextUtils;
import android.content.SharedPreferences.Editor; 
import android.preference.PreferenceManager; 

public class LatinImeReceiver extends BroadcastReceiver {
private static final String TAG = LatinImeReceiver.class.getSimpleName();

private static final String[] DEFAULT_LATIN_IME_LANGUAGES = {"en_US","th"};//默认开启输入法的语言

@Override
public void onReceive(Context context, Intent intent) {
// Set the default input language at the system boot completed.

if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
Log.i(TAG,"onReceive:ACTION_BOOT_COMPLETED");
SharedPreferences sp = context.getSharedPreferences("default_input_language_config", Context.MODE_PRIVATE);
boolean hasSet = sp.getBoolean("has_set", false);

if (!hasSet) {
setDefaultSubtypes(context);
sp.edit().putBoolean("has_set", true).commit();
}
}
}

/**
* M: Set the default IME subtype.
*/

private void setDefaultSubtypes(Context context) { 
Log.i(TAG,"setDefaultSubtypes");
final String serviceName = "com.android.inputmethod.latin/.LatinIME";
final String currentPackageName = "com.android.inputmethod.latin";
final String enable = Settings.Secure.getString(context.getContentResolver(), 
Settings.Secure.ENABLED_INPUT_METHODS);
Log.i(TAG,"enable="+enable);//com.android.inputmethod.latin/.LatinIME

final InputMethodManager imm = (InputMethodManager) context.getSystemService( Context.INPUT_METHOD_SERVICE);
final StringBuilder builder = new StringBuilder();

// Get sub type hash code
for (InputMethodInfo info : imm.getInputMethodList()) {
if (currentPackageName.equals(info.getPackageName())) {
Log.i(TAG,"info.getSubtypeCount()="+info.getSubtypeCount());//55
for (int i = 0; i < info.getSubtypeCount(); i++) { 
final InputMethodSubtype subtype = info.getSubtypeAt(i); 
final String locale = subtype.getLocale().toString();
Log.i(TAG,"locale="+locale);
if (isDefaultLocale(locale)) {
Log.i(TAG, "default enabled subtype locale = " + locale);
builder.append(';');
builder.append(subtype.hashCode());
}
}
break;
}
}

// Insert the sub type 
if (builder.length() > 0 && !TextUtils.isEmpty(enable)) {
final String subtype = builder.toString(); 
builder.setLength(0); 
final int index = enable.indexOf(serviceName) + serviceName.length(); 

if (enable.length() > index) { 
builder.append(enable.substring(0, index)); 
builder.append(subtype); 
builder.append(enable.substring(index)); 
} else if (enable.length() == index) { 
builder.append(enable); 
builder.append(subtype); 
} else { 
return; 
}
} 
else { 
Log.w(TAG, "Build Latin IME subtype failed: " + " builder length = " + builder.length() + 
"; enable isEmpty :" + TextUtils.isEmpty(enable)); 
return; 
}


/*android/packages/inputmethods/LatinIME/java/res/xml/method.xml
-921088104;529847764分别代表en_US和th
*/
Log.i(TAG,"commoit:"+builder.toString());//com.android.inputmethod.latin/.LatinIME;-921088104;529847764

// Commit the result 
android.provider.Settings.Secure.putString(context.getContentResolver(), 
android.provider.Settings.Secure.ENABLED_INPUT_METHODS, builder.toString()); 
String lastInputMethodId = Settings.Secure.getString(context.getContentResolver(), 
Settings.Secure.DEFAULT_INPUT_METHOD); 
Log.w(TAG, "DEFAULT_INPUT_METHOD = " + lastInputMethodId); //com.android.inputmethod.latin/.LatinIME

if(lastInputMethodId.equals(serviceName)) { 
Log.w(TAG, "DEFAULT_INPUT_METHOD = com.android.inputmethod.latin/.LatinIME" );

for (InputMethodInfo info : imm.getInputMethodList()) {
if (currentPackageName.equals(info.getPackageName())) {
for (int i = 0; i < info.getSubtypeCount(); i++) { 
final InputMethodSubtype subtype = info.getSubtypeAt(i);
final String[] locales = DEFAULT_LATIN_IME_LANGUAGES;
Log.w(TAG, "i = " + i + ", locales[0] = " + locales[0]);
if((subtype.getLocale()).equals(locales[0])) {
Log.w(TAG, "putString " + subtype.hashCode());
android.provider.Settings.Secure.putInt(context.getContentResolver(), 
android.provider.Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtype.hashCode());
} 
} 
}
}
}
}

/** 
* M: Check if the current locale is default or not. 
*/ 
private boolean isDefaultLocale (String locale) { 
final String[] locales = DEFAULT_LATIN_IME_LANGUAGES;

for (String s : locales) {
if (s.equals(locale)) {
return true;
}
}

return false; 
}

}


方法二:还有一种修改方案在amlogic验证是OK的

(1) 首先frameworks\base\packages\SettingsProvider\res\values\defaults.xml 增加下面语句
<string name="def_input_methods">com.android.inputmethod.latin/.LatinIME;529847764;-921088104</string>

意思是增加英文和泰文输入法,android/packages/inputmethods/LatinIME/java/res/xml/method.xml中有定义的-921088104;529847764分别代表en_US和th

(2) 然后在frameworks\base\packages\SettingsProvider\src\com\android\providers\settings\DatabaseHelper.java增加如下代码
loadStringSetting(stmt, Settings.Secure.ENABLED_INPUT_METHODS, R.string.def_input_methods);


frameworks/base/packages/SettingsProvider的作用

我们在调用android.provider.Settings修改一些设置时,Settings会调用真正的SettingsProvider去访问数据库。android把SettingsProvider的代码放在了frameworks/base/packages下面。

Android framework系统默认设置修改

修改Settings源码可修改系统设置项,Settings数据被存放于com.android.providers.settings/databases/settings.db 中,如果想修改系统启动后加载的默认值,一种方法是直接修改settings.db的值,另一种就是修改SettingsProvider默认值。

Settings应用能够配置Android系统的各种设置,这些设置的默认值都是由frameworks中的SettingsProvider从数据库中读取的,那么第一次开机的时候这些数据都是从哪儿来的呢?

frameworks/base/packages/SettingsProvider/res/values/defaults.xml这个文件就是用来存储Android系统的默认设置。

如果想定义defaults.xml中没有的,在这里添加后,还需修改frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java,加入自己的存储代码。

例如:
600000设置关屏超时时间的默认值
102 设置亮度的默认值
false设置是否允许安装非Market应用程序的默认值




阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/johnWcheung/article/details/50615584
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭