Honeycom相較與之前的版本,加入了一個.idc後綴的配置文件,使在不修改系統代碼的前提下,我們就可以使用自定義的鍵盤佈局文件,系統中與鍵盤佈局相關的目錄爲/system/usr/keychars,/system/usr/keylayout,/system/usr/idc
一、系統啓動過程中SystemServer添加WindowManagerService
Slog.i(TAG, "Window Manager");
wm = WindowManagerService.main(context, power,
factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL);
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
((ActivityManagerService)ServiceManager.getService("activity"))
.setWindowManager(wm);
private WindowManagerService(Context context, PowerManagerService pm,
……..
……..
mInputManager = new InputManager(context, this); //構造InputManager實例
PolicyThread thr = new PolicyThread(mPolicy, this, context, pm);
thr.start();
synchronized (thr) {
while (!thr.mRunning) {
try {
thr.wait();
} catch (InterruptedException e) {
}
}
}
mInputManager.start(); //調用InputManager.java start()函數
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
}
三、InputManager.java是本地c代碼的包裝類,對com_android_server_InputManager.cpp接口函數進行包裝,以提供其他java文件調取。
1.初始化,InputManager.java構造函數中的init()最後調用nativeInit(mCallbacks),
public InputManager(Context context, WindowManagerService windowManagerService) {
this.mContext = context;
this.mWindowManagerService = windowManagerService;
this.mCallbacks = new Callbacks();
init(); //調用init()函數
}
private void init() {
Slog.i(TAG, "Initializing input manager");
nativeInit(mCallbacks); //java接口,由本地函數實現
}
2. 啓動,InputManager.java的start()最後調用nativeStart():
public void start() {
Slog.i(TAG, "Starting input manager");
nativeStart(); //java接口,由本地函數實現
}
四、com_android_server_InputManager.cpp實現InutManager.java的nativeInit(mCallbacks和nativeStart(),當然還實現了其他功能的接口函數,這裏不再介紹,對於android如何實現java和c之間的轉換,我想對於瞭解jni的來說不難理解。不懂的可以看此文章學習:http://hi.baidu.com/kellyvivian/blog/item/09cfb541179d2f3387947397.html
1.初始化,android_server_InputManager_nativeInit在被執行的時候會new一個NativeInputManager(callbacks)實例,NativeInputManager(callbacks)接着又會new一個InputManager(eventHub,
this, this)實例
static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz,
jobject callbacks) {
if (gNativeInputManager == NULL) {
gNativeInputManager = new NativeInputManager(callbacks);
} else {
LOGE("Input manager already initialized.");
jniThrowRuntimeException(env, "Input manager already initialized.");
}
}
NativeInputManager::NativeInputManager(jobject callbacksObj) :
mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), mVirtualKeyQuietTime(-1),
mMaxEventsPerSecond(-1) {
JNIEnv* env = jniEnv();
mCallbacksObj = env->NewGlobalRef(callbacksObj);
…….
sp<EventHub> eventHub = new EventHub();
mInputManager = new InputManager(eventHub, this, this);
}
2.啓動,android_server_InputManager_nativeStart中gNativeInputManager->getInputManager()->start()最終調用的是InputManager.cpp的start()函數
static void android_server_InputManager_nativeStart(JNIEnv* env, jclass clazz) {
if (checkInputManagerUnitialized(env)) {
return;
}
status_t result = gNativeInputManager->getInputManager()->start();
if (result) {
jniThrowRuntimeException(env, "Input manager could not be started.");
}
}
五、InputManager.cpp中主要有三個函數:initialize()初始化函數,在構造函數中調用;start()開啓線程函數;stop()取消線程函數,在虛構函數中調用。
1.初始化,InputManager.cpp構造函數調用initialize(),期間new一個InputReaderThread線程
InputManager::InputManager(
const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
mDispatcher = new InputDispatcher(dispatcherPolicy);
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
}
void InputManager::initialize() {
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
2.啓動,mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY)開啓初始化時new的InputReaderThread線程
status_t InputManager::start() {
……..
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
if (result) {
LOGE("Could not start InputReader thread due to error %d.", result);
mDispatcherThread->requestExit();
return result;
}
return OK;
}
六、InputReader.cpp中定義了InputReaderThread類,繼承於Thread類
1.初始化,InputReaderThread構造函數,初始化一個Thread類
InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :
Thread(/*canCallJava*/ true), mReader(reader) {
}
2.啓動,run啓動線程,Thread run()方法又調用InputReaderThread 的虛函數threadLoop(),接着調用InputReader的loopOnce()方法,最後調用EventHub.cpp的getEvent(&
rawEvent)方法
bool InputReaderThread::threadLoop() {
mReader->loopOnce();
return true;
}
void InputReader::loopOnce() {
RawEvent rawEvent;
mEventHub->getEvent(& rawEvent);
#if DEBUG_RAW_EVENTS
LOGD("Input event: device=%d type=0x%x scancode=%d keycode=%d value=%d",
rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode,
rawEvent.value);
#endif
process(& rawEvent);
}
七、EventHub.cpp是android輸入系統的硬件抽象層,維護輸入設備的運行,包括Keyboard、 TouchScreen、TraceBall等。
EventHub.cpp中依次執行getEvent()–>openPlatformInput()–>scanDir(DEVICE_PATH)–> openDevice(devname)
bool EventHub::openPlatformInput(void) {
/*
* Open platform-specific input device(s).
*/
int res, fd;
………
// Reserve fd index 0 for inotify.
struct pollfd pollfd;
pollfd.fd = fd;
pollfd.events = POLLIN;
pollfd.revents = 0;
mFds.push(pollfd);
mDevices.push(NULL);
res = scanDir(DEVICE_PATH); //DEVICE_PATH = "/dev/input"
if(res < 0) {
LOGE("scan dir failed for %s\n", DEVICE_PATH);
}
return true;
}
int EventHub::scanDir(const char *dirname)
{
……
openDevice(devname);
}
closedir(dir);
return 0;
}
openDevice方法會打開/dev/input目錄下的所有設備文件,讀取name、version、id等設備信息,然後執行loadConfiguration()方法,如果鍵盤設備就會執行loadKeyMap()這個方法
int EventHub::openDevice(const char *devicePath) {
……
// Load the configuration file for the device.
loadConfiguration(device);
……
if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) {
// Load the keymap for the device.
status_t status = loadKeyMap(device);
……
}
……
}
Honeycomb與之前版本不同之處是加入loadConfiguration()方法,它獲取與當前設備驅動Vendor、Product、Version匹配的配置文件名,或者是Vendor、Product匹配的配置文件名,具體可查看Input.cpp中getInputDeviceConfigurationFilePathByDeviceIdentifie和getInputDeviceConfigurationFilePathByName方法。
如: kernel/ drivers/input/keyboard/atkbd.c鍵盤驅動中定義了 input_dev->id.vendor = 0×0001; input_dev->id.product = 0×0001;
input_dev->id.version = 0xab41,那麼與之對應的配置名爲Vendor_0001_Product_0001_Version_ad41.idc,返回這個文件的全路徑並賦值給device->configurationFile。如果/system/user/idc下存在此文件,接下來調用PropertyMap.cpp的load()方法解析該配置文件並將解析後的信息保存到device->configuration中。
void EventHub::loadConfiguration(Device* device) {
device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier(
device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION);
if (device->configurationFile.isEmpty()) {
LOGD("No input device configuration file found for device ‘%s’.",
device->identifier.name.string());
} else {
status_t status = PropertyMap::load(device->configurationFile,
&device->configuration);
if (status) {
LOGE("Error loading input device configuration file for device ‘%s’. "
"Using default configuration.",
device->identifier.name.string());
}
}
}
EventHub.cpp中loadKeyMap又調用了Keyboard.cpp的KeyMap::load()方法
status_t EventHub::loadKeyMap(Device* device) {
return device->keyMap.load(device->identifier, device->configuration);
}
八、在Keyboard.cpp的load方法中,首先判斷deviceConfiguration參數是否爲空,deviceConfiguration的賦值就是上面loadConfiguration()方法所做的工作。
如果有.idc的配置文件,那麼獲取key爲keyboard.layout的value給keyLayoutName和key爲keyboard.characterMap的value給keyCharacterMapName,最後調用loadKeyLayout和loadKeyCharacterMap方法加載此鍵盤佈局文件;如果沒有對應的.idc配置文件,則deviceConfiguration爲空,就會接着執行probeKeyMap(deviceIdenfifier,
String8("Generic"))方法
status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier,
const PropertyMap* deviceConfiguration) {
// Use the configured key layout if available.
if (deviceConfiguration) {
String8 keyLayoutName;
if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"),
keyLayoutName)) {
status_t status = loadKeyLayout(deviceIdenfifier, keyLayoutName);
if (status == NAME_NOT_FOUND) {
LOGE("Configuration for keyboard device ‘%s’ requested keyboard layout ‘%s’ but "
"it was not found.",
deviceIdenfifier.name.string(), keyLayoutName.string());
}
}
String8 keyCharacterMapName;
if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
keyCharacterMapName)) {
status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName);
if (status == NAME_NOT_FOUND) {
LOGE("Configuration for keyboard device ‘%s’ requested keyboard character "
"map ‘%s’ but it was not found.",
deviceIdenfifier.name.string(), keyLayoutName.string());
}
}
if (isComplete()) {
return OK;
}
}
……
if (probeKeyMap(deviceIdenfifier, String8("Generic"))) {
return OK;
}
……
}
probeKeyMap方法判斷名爲Gerneric的佈局文件是否存在,若存在就會調用loadKeyLayout和loadKeyCharacterMap方法加載此鍵盤佈局文件
bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier,
const String8& keyMapName) {
if (!haveKeyLayout()) {
loadKeyLayout(deviceIdentifier, keyMapName);
}
if (!haveKeyCharacterMap()) {
loadKeyCharacterMap(deviceIdentifier, keyMapName);
}
return isComplete();
}
至此,Android Honeycomb已經正確加載了鍵盤佈局文件,那麼我們如何定製和使用自己的鍵盤佈局文件呢?
附件:qwerty.idc配置文件內容
# Copyright (C) 2010 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Emulator keyboard configuration file #1.
#
touch.deviceType = touchScreen
touch.orientationAware = 1
keyboard.layout = qwerty
keyboard.characterMap = qwerty
keyboard.orientationAware = 1
keyboard.builtIn = 1
cursor.mode = navigation
cursor.orientationAware = 1