Android Honeycomb加载键盘布局文件过程

Andriod启动过程中是如何正确加载.kl和.kcm的键盘布局文件?本文就从Honeycomb代码入手,详细介绍开机启动时键盘布局文件的加载过程。 

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);
二、WindowManagerService.java的构造函数,在加载键盘布局方面做了两件事情:1.初始化,构造一个InputManager实例;2.启动,由InputManager.java start ()函数实现  

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 = 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& eventHub, 
        const sp& readerPolicy, 
        const sp& 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& 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

转自:http://zhougaofeng.ixiezi.com/2011/04/19/honeycomb-keylayout/

你可能感兴趣的:(android)