Input原始碼解讀——從"Show tabs"開始

Aloys_Code發表於2023-01-08

Input原始碼解讀——從"Show tabs"開始

本文基於Android T版本原始碼,梳理當使用者在開發者選項中開啟Show tabs功能後顯示第點按操作的視覺反饋的原理,來進一步瞭解Android Input系統

Settings 寫入設定

首先是設定應用(Settings)提供的開發者選項畫面響應點選,將Show taps選項對應的設定Key SHOW_TOUCHES的 ON 值透過android.provder.Settings介面寫入到儲存系統設定資料的SettingsProvier中。

// packages/apps/Settings/src/com/android/settings/development/ShowTapsPreferenceController.java
public class ShowTapsPreferenceController extends DeveloperOptionsPreferenceController ... {
    ...
    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        final boolean isEnabled = (Boolean) newValue;
        Settings.System.putInt(mContext.getContentResolver(),
                Settings.System.SHOW_TOUCHES, isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF);
        return true;
    }
    ...
}

InputManagerService監聽設定

負責管理輸入的系統服務InputManagerService在啟動之際,會監聽設定中的 SHOW_TOUCHES欄位的變化,在設定產生變化的時候呼叫native側的程式碼進行處理。

// frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
public class InputManagerService extends IInputManager.Stub... {
    ...
    public void start() {
        ...
        registerShowTouchesSettingObserver();
        ...
    }
    
    private void registerShowTouchesSettingObserver() {
        mContext.getContentResolver().registerContentObserver(
                Settings.System.getUriFor(Settings.System.SHOW_TOUCHES), true,
                new ContentObserver(mHandler) {
                    @Override
                    public void onChange(boolean selfChange) {
                        updateShowTouchesFromSettings();
                    }
                }, UserHandle.USER_ALL);
    }
    
    private void updateShowTouchesFromSettings() {
        int setting = getShowTouchesSetting(0);
        mNative.setShowTouches(setting != 0);
    }
    ...

// frameworks/base/services/core/java/com/android/server/input/NativeInputManagerService.java
public interface NativeInputManagerService {
    ...
    void setShowTouches(boolean enabled);
    ...
}
// frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
class NativeInputManager : public virtual RefBase, ...{
    ...
    void setShowTouches(bool enabled);
    ...
}

void NativeInputManager::setShowTouches(bool enabled) {
    { // acquire lock
        AutoMutex _l(mLock);

        if (mLocked.showTouches == enabled) {
            return;
        }

        ALOGI("Setting show touches feature to %s.", enabled ? "enabled" : "disabled");
        mLocked.showTouches = enabled;
    } // release lock

    mInputManager->getReader().requestRefreshConfiguration(
            InputReaderConfiguration::CHANGE_SHOW_TOUCHES);
}

這裡的mInputManagerInputManagerInterface物件例項,InputManagerInputManagerInterface和子類,所以透過mInputManager可以連線NativeInputManagerInputReader

這裡向負責讀取事件的InputReader發出更新配置的請求,配置變更的TypeCHANGE_SHOW_TOUCHES

透過 InputReader 請求重新整理配置

InputReader接收到配置變化的Type之後,會根據記錄待重新整理配置的變數 mConfigurationChangesToRefresh判斷當前是否已經在重新整理過程中。
如果尚未處於重新整理中,則更新mConfigurationChangesToRefresh的值,並喚醒EventHub進行配置重新整理。

// frameworks/native/services/inputflinger/reader/InputReader.cpp
void InputReader::requestRefreshConfiguration(uint32_t changes) {
    std::scoped_lock _l(mLock);

    if (changes) {
        bool needWake = !mConfigurationChangesToRefresh;
        mConfigurationChangesToRefresh |= changes;

        if (needWake) {
            mEventHub->wake();
        }
    }
}

EventHub 喚醒 InputReader 執行緒

InputManagerService過來的重新整理請求最終需要InputReader執行緒來處理。
可是 InputReader 執行緒處在從EventHub中讀取事件和沒有事件時便呼叫epoll_wait進入等待狀態的迴圈當中。
所以為了讓其立即處理配置變化,需要EventHub的手動喚醒。

// frameworks/native/services/inputflinger/reader/EventHub.cpp
void EventHub::wake() {
    ALOGV("wake() called");

    ssize_t nWrite;
    do {
        nWrite = write(mWakeWritePipeFd, "W", 1);
    } while (nWrite == -1 && errno == EINTR);

    if (nWrite != 1 && errno != EAGAIN) {
        ALOGW("Could not write wake signal: %s", strerror(errno));
    }
}

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    ...
    for (;;) {
        ...
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
        ...
    }
    ...
}

InputReader執行緒重新整理配置

EventHub喚醒後處於等待狀態的getEvents會結束,之後InputReader執行緒會進入下次迴圈即loopOnce
其首先將檢查是否存在待重新整理的配置變化changes,存在的話呼叫refreshConfigurationLockedInputDevice去重新適配變化。

// frameworks/native/services/inputflinger/reader/InputReader.cpp
void InputReader::loopOnce() {
    ...
    std::vector<InputDeviceInfo> inputDevices;
    { // acquire lock
        ...
        uint32_t changes = mConfigurationChangesToRefresh;
        if (changes) {
            mConfigurationChangesToRefresh = 0;
            timeoutMillis = 0;
            refreshConfigurationLocked(changes);
        } else if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
        }
    } // release lock

    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    ...
}

需要留意,refreshConfigurationLocked在呼叫InputDevice進一步處理之前需要先獲取配置的變化放入mConfig中。

// frameworks/native/services/inputflinger/reader/InputReader.cpp
void InputReader::refreshConfigurationLocked(uint32_t changes) {
    mPolicy->getReaderConfiguration(&mConfig);
    ...

    if (changes & InputReaderConfiguration::CHANGE_MUST_REOPEN) {
        mEventHub->requestReopenDevices();
    } else {
        for (auto& devicePair : mDevices) {
            std::shared_ptr<InputDevice>& device = devicePair.second;
            device->configure(now, &mConfig, changes);
        }
    }
    ...
}

InputDevice配置變化

InputDeviceconfigure需要處理很多配置變化,比如鍵盤佈局、麥克風等。對於Show taps的變化關注呼叫 InputMappercongfigure即可。

// frameworks/native/services/inputflinger/reader/InputDevice.cpp
void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config,
                            uint32_t changes) {
    ...
    if (!isIgnored()) {
        ...
        for_each_mapper([this, when, config, changes](InputMapper& mapper) {
            mapper.configure(when, config, changes);
            mSources |= mapper.getSources();
        });
        ...
    }
}

TouchInputMapper 進一步處理

眾多輸入事件的物理資料需要對應的InputMapper來轉化為上層能識別的事件型別。比如識別鍵盤輸入的 KeyboardInputMapper、識別震動的VibratorInputMapper等等。

現在的觸控式螢幕都支援多點觸控,所以是MultiTouchInputMapper來處理的。可MultiTouchInputMapper沒有複寫 configure(),而是沿用由父類TouchInputMapper的共通處理。

// frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp
void TouchInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
                                 uint32_t changes) {
    ...
    bool resetNeeded = false;
    if (!changes ||
        (changes &
         (InputReaderConfiguration::CHANGE_DISPLAY_INFO |
          InputReaderConfiguration::CHANGE_POINTER_CAPTURE |
          InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT |
          InputReaderConfiguration::CHANGE_SHOW_TOUCHES |
          InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE))) {
        // Configure device sources, display dimensions, orientation and
        // scaling factors.
        configureInputDevice(when, &resetNeeded);
    }
    ...
}

TouchInputMapper會依據changes的型別進行對應處理,對於SHOW_TOUCHES的變化需要呼叫configureInputDevice進一步處理。

建立和初始化 PointerController

configureInputDevice進行多個引數的測量和配置,其中和Show taps相關的是PointerController的建立,該類是 Mouse、Taps、Pointer location 等系統 Touch 顯示的專用類。

// frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp
void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) {
    ...
    // Create pointer controller if needed, and keep it around if Pointer Capture is enabled to
    // preserve the cursor position.
    if (mDeviceMode == DeviceMode::POINTER ||
        (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) ||
        (mParameters.deviceType == Parameters::DeviceType::POINTER &&
         mConfig.pointerCaptureRequest.enable)) {
        if (mPointerController == nullptr) {
            mPointerController = getContext()->getPointerController(getDeviceId());
        }
        if (mConfig.pointerCaptureRequest.enable) {
            mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
        }
    } else {
        mPointerController.reset();
    }
    ...
}

這裡呼叫InputReaderContext#getPointerControllerInputReader::ContextImplInputReaderContext的子類,所以會回撥到InputReader開啟PointerController的建立和初始化。

// frameworks/native/services/inputflinger/reader/InputReader.cpp
std::shared_ptr<PointerControllerInterface> InputReader::ContextImpl::getPointerController(
        int32_t deviceId) {
    // lock is already held by the input loop
    return mReader->getPointerControllerLocked(deviceId);
}

std::shared_ptr<PointerControllerInterface> InputReader::getPointerControllerLocked(
        int32_t deviceId) {
    std::shared_ptr<PointerControllerInterface> controller = mPointerController.lock();
    if (controller == nullptr) {
        controller = mPolicy->obtainPointerController(deviceId);
        mPointerController = controller;
        updatePointerDisplayLocked();
    }
    return controller;
}

這裡呼叫InputReaderPolicyInterface#obtainPointerController,而NativeInputManagerInputReaderPolicyInterface的子類。

// frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerController(
        int32_t /* deviceId */) {
    ...
    std::shared_ptr<PointerController> controller = mLocked.pointerController.lock();
    if (controller == nullptr) {
        ensureSpriteControllerLocked();

        controller = PointerController::create(this, mLooper, mLocked.spriteController);
        mLocked.pointerController = controller;
        updateInactivityTimeoutLocked();
    }

    return controller;
}

PointerController 構建的同時需要構建持有的 MouseCursorController。

// frameworks/base/libs/input/PointerController.cpp
std::shared_ptr<PointerController> PointerController::create( ... ) {
    std::shared_ptr<PointerController> controller = std::shared_ptr<PointerController>(
            new PointerController(policy, looper, spriteController));
    ...
    return controller;
}

PointerController::PointerController( ... )
      : mContext(policy, looper, spriteController, *this), mCursorController(mContext) {
    std::scoped_lock lock(mLock);
    mLocked.presentation = Presentation::SPOT;
    ...
}

obtainPointerController執行完之後呼叫updatePointerDisplayLocked執行PointerController的初始化。

初始化 PointerController

呼叫PointerControllersetDisplayViewport傳入顯示用的DisplayViewPort

// frameworks/native/services/inputflinger/reader/InputReader.cpp
void InputReader::updatePointerDisplayLocked() {
    ...
    std::optional<DisplayViewport> viewport =
            mConfig.getDisplayViewportById(mConfig.defaultPointerDisplayId);
    if (!viewport) {
        ...
        viewport = mConfig.getDisplayViewportById(ADISPLAY_ID_DEFAULT);
    }
    ...
    controller->setDisplayViewport(*viewport);
}

setDisplayViewport需要持有的MouseCursorController進一步初始化。

// frameworks/base/libs/input/PointerController.cpp
void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
    ...
    mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
}

MouseCursorController需要獲取Display相關的引數,並執行兩個重要步驟:loadResourcesLocked/updatePointerLocked

// frameworks/base/libs/input/MouseCursorController.cpp
void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport,
                                               bool getAdditionalMouseResources) {
    ...
    // Reset cursor position to center if size or display changed.
    if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth ||
        oldDisplayHeight != newDisplayHeight) {
        float minX, minY, maxX, maxY;
        if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
            mLocked.pointerX = (minX + maxX) * 0.5f;
            mLocked.pointerY = (minY + maxY) * 0.5f;
            // Reload icon resources for density may be changed.
            loadResourcesLocked(getAdditionalMouseResources);
        ...
        }
    } else if (oldViewport.orientation != viewport.orientation) {
        ...
    }

    updatePointerLocked();
}

載入 Pointer 相關資源

// frameworks/base/libs/input/MouseCursorController.cpp
void MouseCursorController::loadResourcesLocked(bool getAdditionalMouseResources) REQUIRES(mLock) {
    ...
    policy->loadPointerResources(&mResources, mLocked.viewport.displayId);
    policy->loadPointerIcon(&mLocked.pointerIcon, mLocked.viewport.displayId);
    ...
}

省略諸多細節,loadPointerResources將透過InputManagerServiceJNI端以及PointerIconJNI端建立PointerIcon例項,並讀取顯示的資源。

getSystemIcon則是負責的函式,其將讀取系統資源里名為PointerStyle,並讀取指標對應的資源 ID。

// frameworks/base/core/java/android/view/PointerIcon.java
    public static PointerIcon getSystemIcon(@NonNull Context context, int type) {
        ...
        int typeIndex = getSystemIconTypeIndex(type);
        if (typeIndex == 0) {
            typeIndex = getSystemIconTypeIndex(TYPE_DEFAULT);
        }

        int defStyle = sUseLargeIcons ?
                com.android.internal.R.style.LargePointer : com.android.internal.R.style.Pointer;
        TypedArray a = context.obtainStyledAttributes(null,
                com.android.internal.R.styleable.Pointer,
                0, defStyle);
        int resourceId = a.getResourceId(typeIndex, -1);
        ...
        icon = new PointerIcon(type);
        if ((resourceId & 0xff000000) == 0x01000000) {
            icon.mSystemIconResourceId = resourceId;
        } else {
            icon.loadResource(context, context.getResources(), resourceId);
        }
        systemIcons.append(type, icon);
        return icon;
    }

    private static int getSystemIconTypeIndex(int type) {
        switch (type) {
            ...
            case TYPE_SPOT_TOUCH:
                return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch;
            ...
            default:
                return 0;
        }
    }

資源 ID 為 pointer_spot_touch_icon。

<!-- frameworks/base/core/res/res/drawable/pointer_spot_touch_icon.xml -->
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
    android:bitmap="@drawable/pointer_spot_touch"
    android:hotSpotX="16dp"
    android:hotSpotY="16dp" />

其指向的圖片就是如下熟悉的 Spot png:pointer_spot_touch.png。之後的loadPointerIcon階段會將該圖片解析成 Bitmap 並被管理在SpriteIcon中。

SpriteIconupdatePointerLocked階段會被存放到SpriteController中,等待顯示的排程。

// frameworks/base/libs/input/MouseCursorController.cpp
void MouseCursorController::updatePointerLocked() REQUIRES(mLock) {
    if (!mLocked.viewport.isValid()) {
        return;
    }
    sp<SpriteController> spriteController = mContext.getSpriteController();
    spriteController->openTransaction();

    ...
    if (mLocked.updatePointerIcon) {
        if (mLocked.requestedPointerType == mContext.getPolicy()->getDefaultPointerIconId()) {
            mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
        ...
        }
        mLocked.updatePointerIcon = false;
    }

    spriteController->closeTransaction();
}

顯示Tap

點選的時候EventHub#getEvents會產生事件,InputReader#loopOnce會呼叫processEventsLocked處理事件。

// frameworks/native/services/inputflinger/reader/InputReader.cpp
void InputReader::loopOnce() {
    ...
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    { // acquire lock
        ...
        if (count) {
            processEventsLocked(mEventBuffer, count);
        }
        ....
    } // release lock
    ...
}

之後呼叫InputMapper開始加工事件,並在TouchInputMapper#cookAndDispatch的時候呼叫updateTouchSpots更新 PointerController的一些引數。

// frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp
void TouchInputMapper::updateTouchSpots() {
    ...
    mPointerController->setPresentation(PointerControllerInterface::Presentation::SPOT);
    mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);

    mPointerController->setButtonState(mCurrentRawState.buttonState);
    setTouchSpots(mCurrentCookedState.cookedPointerData.pointerCoords,
                  mCurrentCookedState.cookedPointerData.idToIndex,
                  mCurrentCookedState.cookedPointerData.touchingIdBits, mViewport.displayId);
}

其中比較關鍵的setTouchSpots是顯示Taps的關鍵步驟,準備 x、y 座標和壓力值。

在 Reader 而不是 Dispatch、更不是 ViewRootImpl 的時候處理的原因在於:Read 到事件即顯示可以更早地響,同時不用佔用 App 程式。

// frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp
void TouchInputMapper::setTouchSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
                                     BitSet32 spotIdBits, int32_t displayId) {
    std::array<PointerCoords, MAX_POINTERS> outSpotCoords{};

    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
        const uint32_t index = spotIdToIndex[idBits.clearFirstMarkedBit()];
        float x = spotCoords[index].getX();
        float y = spotCoords[index].getY();
        float pressure = spotCoords[index].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
        ...
    }

    mPointerController->setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits, displayId);
}

其後PointerController會透過TouchSpotController建立Spot例項向其傳送updateSprite請求。最後回撥 SpriteController呼叫setIcon處理。

// frameworks/base/libs/input/TouchSpotController.cpp
void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float x, float y,
                                             int32_t displayId) {
    sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
    ...
    if (icon != mLastIcon) {
        mLastIcon = icon;
        if (icon) {
            sprite->setIcon(*icon);
            sprite->setVisible(true);
        } else {
            sprite->setVisible(false);
        }
    }
}
// frameworks/base/libs/input/SpriteController.cpp
void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) {
    AutoMutex _l(mController->mLock);
    ...
    invalidateLocked(dirty);
}

void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
    ...
    if (!wasDirty) {
        mController->invalidateSpriteLocked(this);
    }
}

void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
    bool wasEmpty = mLocked.invalidatedSprites.isEmpty();
    mLocked.invalidatedSprites.push(sprite);
    if (wasEmpty) {
        if (mLocked.transactionNestingCount != 0) {
            mLocked.deferredSpriteUpdate = true;
        } else {
            mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
        }
    }
}

MSG_UPDATE_SPRITES經過 Handler 回撥doUpdateSprites,將取出封裝在SpriteUpdate中的SpriteIcon並執行 draw。

// frameworks/base/libs/input/SpriteController.cpp
void SpriteController::doUpdateSprites() {
    ...
    for (size_t i = 0; i < numSprites; i++) {
        SpriteUpdate& update = updates.editItemAt(i);

        if ((update.state.dirty & DIRTY_BITMAP) && update.state.surfaceDrawn) {
            update.state.surfaceDrawn = false;
            update.surfaceChanged = surfaceChanged = true;
        }

        if (update.state.surfaceControl != NULL && !update.state.surfaceDrawn
                && update.state.wantSurfaceVisible()) {
            sp<Surface> surface = update.state.surfaceControl->getSurface();
            if (update.state.icon.draw(surface)) {
                update.state.surfaceDrawn = true;
                update.surfaceChanged = surfaceChanged = true;
            }
        }
    }
    ...
    updates.clear();
}

最後,SpriteIcon將取出Bitmap描畫到SurfaceCanvas上去。

// frameworks/base/libs/input/SpriteIcon.cpp
bool SpriteIcon::draw(sp<Surface> surface) const {
    ...
    graphics::Paint paint;
    paint.setBlendMode(ABLEND_MODE_SRC);

    graphics::Canvas canvas(outBuffer, (int32_t)surface->getBuffersDataSpace());
    canvas.drawBitmap(bitmap, 0, 0, &paint);
    ...
    status = surface->unlockAndPost();
    if (status) {
        ALOGE("Error %d unlocking and posting sprite surface after drawing.", status);
    }
    return !status;
}

總體流程

透過一個框圖簡單回顧一下整個流程。

可以看到,簡簡單單的 Show taps 功能,從設定、配置、重新整理再到顯示,經歷了多個程式、多個模組的協力。

涉及的Input核心邏輯框圖

相關文章