天天看點

Android Honeycomb加載鍵盤布局檔案過程

原文位址:http://zhougaofeng.ixiezi.com/2011/04/19/honeycomb-keylayout/

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> 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( 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) {

    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

轉載于:https://www.cnblogs.com/jack2010/archive/2012/05/24/2516822.html