Android 源码分析 - 输入 - 流程

设备加入

  • EventHub检测到设备加入(getEvents)
  1. if (eventItem.data.u32 == EPOLL_ID_INOTIFY)
    1. mPendingINotify = true【延迟到其他事件后处理】
  2. if (mPendingINotify && mPendingEventIndex >= mPendingEventCount)【其他事件处理完了 】
    1. mPendingINotify = false
    2. readNotifyLocked()
      1. read()【读取inotify句柄】
      2. if(event->mask & IN_CREATE)
        1. openDeviceLocked()
  • EventHub打开设备(openDeviceLocked)
  1. open()【打开设备文件】
  2. ioctl()【读取名字,EVIOCGNAME】
  3. 检查是否排除(mExcludedDevices)
    1. close()【如果是,关闭设备文件】
  4. 读取设备信息
    1. ioctl()【读取设备驱动版本,EVIOCGVERSION】
    2. ioctl()【读取设备input_id,包括总线、产品号、厂商、版本,EVIOCGID】
    3. ioctl()【读取设备硬件位置,EVIOCGPHYS】
    4. ioctl()【读取设备GUID,EVIOCGUNIQ】
  5. setDescriptor()【计算descriptor,用sha1作用于几个字段的组合】
  6. fcntl()【设置非阻塞,F_SETFL】
  7. new Device【deviceId=mNextDeviceId++】
  8. loadConfigurationLocked()【加载设备配置,*.idc文件】
    1. getInputDeviceConfigurationFilePathByDeviceIdentifier()【libinput】
    2. PropertyMap::load()【解析配置文件】
  9. 检查设备的输入事件类型
    1. ioctl()【EVIOCGBIT,EV_KEY、EV_ABS、EV_REL、EV_SW、EV_LED、EV_FF】
    2. ioctl()【EVIOCGPROP】
  10. 【判断设备的类别,比如INPUT_DEVICE_CLASS_KEYBOARD】
    1. haveKeyboardKeys【keyBitmask:0~BTN_MISC,KEY_OK~KEY_MAX+1】
    2. haveGamepadButtons【keyBitmask:BTN_MISC~BTN_MOUSE,BTN_JOYSTICK~BTN_DIGI,两组游戏杆按钮】
    3. haveKeyboardKeys || haveGamepadButtons
      1. classes  |= INPUT_DEVICE_CLASS_KEYBOARD
    4. BTN_MOUSE && REL_X && REL_Y
      1. classes |= INPUT_DEVICE_CLASS_CURSOR
  11. if (classes & INPUT_DEVICE_CLASS_TOUCH)
    1. loadVirtualKeyMapLocked()
      1. VirtualKeyMap::load(/sys/board_properties/virtualkeys.<name>)
    2. 【如果失败】
      1.  classes |= INPUT_DEVICE_CLASS_KEYBOARD
  12. if (classes & (INPUT_DEVICE_CLASS_KEYBOARD|INPUT_DEVICE_CLASS_JOYSTICK))
    1. loadKeyMapLocked()
      1. keyMap.load()【加载*.kl,*.kcm】
  13. if (classes & INPUT_DEVICE_CLASS_KEYBOARD)
    1. isEligibleBuiltInKeyboard()
    2. mBuiltInKeyboardId = id 【第一个,只有一个内建输入设备】
    3. hasKeycodeLocked(AKEYCODE_Q)(由AKC反向查找LKC集,并且keyBitmask至少有LKC集里面一个)
      1. classes |= INPUT_DEVICE_CLASS_ALPHAKEY
    4. ioctl()【禁止内核重复key事件,EVIOCSREP】
  14. isExternalDeviceLocked()(${device.internal},BUS_USB、BUS_BLUETOOTH)
    1. classes |= INPUT_DEVICE_CLASS_EXTERNAL
  15. if (classes & (INPUT_DEVICE_CLASS_JOYSTICK|INPUT_DEVICE_CLASS_GAMEPAD))
    1. controllerNumber = getNextControllerNumberLocked()
  16. epoll_ctl()(注册到epoll池中)
  17. ioctl()【设置唤醒锁,EVIOCSSUSPENDBLOCK】
  18. ioctl()【设置时间戳使用monotonic时钟,EVIOCSCLOCKID】
  19. addDeviceLocked()(添加设备)
    1. mDevices.add()
    2. mOpeningDevices【加入正在打开的设备列表,单向链表】
  • EventHub返回设备加入事件(getEvents)
  1. while (mOpeningDevices != NULL)
    1. event = ??
    2. event += 1
    3. mNeedToSendFinishedDeviceScan = true
  2. if (mNeedToSendFinishedDeviceScan)
    1. event = ??
    2. event += 1
  • InputReader处理设备增加事件(loopOnce)
  1. mEventHub->getEvents()
  2. processEventsLocked()
    1. if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT)
    2. else
      1. switch (rawEvent->type)
        1. case EventHubInterface::DEVICE_ADDED
          1. addDeviceLocked()
  • InputReader增加设备(addDeviceLocked)
  1. createDeviceLocked()
    1. new InputDevice
    2. if (classes & INPUT_DEVICE_CLASS_SWITCH)
      1. device->addMapper(new SwitchInputMapper)
    3. if (classes & INPUT_DEVICE_CLASS_VIBRATOR)
      1. device->addMapper(new VibratorInputMapper)
    4. 转换classes到keyboardSource和keyboardType
    5. if (keyboardSource != 0)
      1. device->addMapper(new KeyboardInputMapper)
    6. if (classes & INPUT_DEVICE_CLASS_CURSOR)
      1. device->addMapper(new CursorInputMapper)
    7. if (classes & INPUT_DEVICE_CLASS_TOUCH_MT)
      1. device->addMapper(new MultiTouchInputMapper)
    8. else if (classes & INPUT_DEVICE_CLASS_TOUCH)
      1. device->addMapper(new SingleTouchInputMapper)
    9. if (classes & INPUT_DEVICE_CLASS_JOYSTICK)
      1. device->addMapper(new JoystickInputMapper)
  2. mDevices.add()
  3. bumpGenerationLocked()
    1. ++mGeneration
  • InputReader增加设备后续处理(loopOnce)
  1. if (oldGeneration != mGeneration)
    1. inputDevicesChanged = true
    2. getInputDevicesLocked(inputDevices)【获取所有设备信息】
      1. forall mDevices()
        1. device->getDeviceInfo()【填充InputDeviceInfo结构】
          1. forall mMappers
            1. mapper->populateDeviceInfo()
  2. if (inputDevicesChanged)
    1. mPolicy->notifyInputDevicesChanged【InputReaderPolicyInterface】

设备删除

  • EventHub检测到设备加入(getEvents)
  1. if (eventItem.data.u32 == EPOLL_ID_INOTIFY)
    1. mPendingINotify = true【延迟到其他事件后处理】
  2. if (mPendingINotify && mPendingEventIndex >= mPendingEventCount)【其他事件处理完成】
    1. mPendingINotify = false
    2. readNotifyLocked()
      1. read()
      2. if(event->mask & IN_CREATE)
      3. else
        1. closeDeviceByPathLocked()
          1. getDeviceByPathLocked()
          2. closeDeviceLocked()
  • EventHub关闭设备(closeDeviceLocked)
  1. if (device->id == mBuiltInKeyboardId)
    1. mBuiltInKeyboardId = NO_BUILT_IN_KEYBOARD
  2. if (!device->isVirtual())【fd < 0】
    1. epoll_ctl()【从epoll池中,EPOLL_CTL_DEL】
  3. releaseControllerNumberLocked()
  4. mDevices.removeItem()
  5. device->close()
    1. ::close()
  6. mOpeningDevices()【从正在打开列表中删除】
  7. mClosingDevices()【进入正在关闭列表,单向链表】
  • EventHub返回设备删除事件(getEvents)
  1. while (mClosingDevices)
    1. event = ??
    2. event += 1
    3. mNeedToSendFinishedDeviceScan = true
  2. if (mNeedToSendFinishedDeviceScan)
    1. event = ??
    2. event += 1
  • InputReader处理设备删除事件(loopOnce)
  1. mEventHub->getEvents()
  2. processEventsLocked()
    1. if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT)
    2. else
      1. switch (rawEvent->type)
        1. case EventHubInterface::DEVICE_REMOVED
          1. removeDeviceLocked()
  • InputReader删除设备(removeDeviceLocked)
  1. mDevices.removeItemsAt()
  2. bumpGenerationLocked()
    1. ++mGeneration
  3. device->reset()【重置设备】
    1. forall mMappers
      1. mapper->reset()
    2. mContext->updateGlobalMetaState()【更新GlobalMetaState】
      1. InputReader::updateGlobalMetaStateLocked()
        1. forall mDevices
          1. mGlobalMetaState |= device->getMetaState()
    3. notifyReset()【增加设备重置事件】
      1. mContext->getListener()->notifyDeviceReset()
        1. QueuedInputListener->notifyDeviceReset()
          1. mArgsQueue.push(new NotifyDeviceResetArgs()
  4. delete device
  • InputReader删除设备后续处理(loopOnce)
  1. if (oldGeneration != mGeneration)
    1. inputDevicesChanged = true
    2. getInputDevicesLocked(inputDevices)【获取所有设备信息】
  2. if (inputDevicesChanged)
    1. mPolicy->notifyInputDevicesChanged()【InputReaderPolicyInterface】

输入事件(Native层)

  • EventHub收到设备输入事件(getEvents)
  1. mDevices.indexOfKey()
  2. mDevices.valueAt()
  3. if (eventItem.events & EPOLLHUP)
    1. read()【读取设备输入事件,事件数不超过剩余空间】
    2. if (errno == ENODEV)
      1. deviceChanged = true
      2. closeDeviceLocked()
    3. else
      1. event = ??
      2. event += 1
  4. else if (eventItem.events & EPOLLHUP)
    1. deviceChanged = true
    2. closeDeviceLocked()
  • InputReader处理设备输入事件(loopOnce)
  1. mEventHub->getEvents()【步骤一】
  2. processEventsLocked()
    1. if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT)
      1. batchSize【搜索后续相同设备的输入事件】
      2. processEventsForDeviceLocked()
        1. mDevices.valueAt()【工具id查找设备】
        2. device->process()【InputDevice, 步骤三】
    2. mQueuedListener->flush()
      1. foreach mArgsQueue【针对事件一个个回调】
        1. args->notify
          1. InputListenerInterface->notify*(InputDispatcher)
  • InputDevice处理设备输入事件(process)
  1. if (mDropUntilNextSync)【SYN_DROPPED到SYN_REPORT全部丢弃】
    1. if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT)
      1. mDropUntilNextSync = false
  2. else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED)
    1. mDropUntilNextSync = true
    2. reset()【状态重置,见流程“设备删除”】
  3. else
    1. forall mMappers【设备上的所有Mapper处理】
      1. mapper->process()【累积事件直到EV_SYN/SYN_REPORT】
        1. getListener()->notify*()【QueuedInputListener排队】
  • InputDispatcher排队输入事件
  1. notifyKey()【键击事件】
    1. KeyEvent.initialize()
    2. mPolicy->interceptKeyBeforeQueueing()【策略预处理】
    3. if (shouldSendKeyToInputFilterLocked)【mInputFilterEnabled】
      1. if (!mPolicy->filterInputEvent)
        1. return
    4. new KeyEntry【创建一个键击事件节点】
    5. enqueueInboundEventLocked()【插入队列,返回是否需要唤醒,needWake】
      1. mInboundQueue.isEmpty()【如果队列原来为空,唤醒,needWake】
      2. mInboundQueue.enqueueAtTail()
      3. isAppSwitchKeyEventLocked()【如果应用切换,唤醒】
      4. findTouchedWindowAtLocked()【如果触摸另一个应用窗口,唤醒】
    6. if (needWake)
      1. mLooper->wake(唤醒分派线程)
  2. notifyMotion()【移动事件】
    1. mPolicy->interceptMotionBeforeQueueing()【策略预处理】
    2. if (shouldSendMotionToInputFilterLocked)
      1. MotionEvent.initialize()
      2. if (!mPolicy->filterInputEvent)
        1. return
    3. new MotionEntry【创建一个移动事件节点】
    4. enqueueInboundEventLocked()【同上】
    5. if (needWake)
      1. mLooper->wake()
  3. notifySwitch()【开关事件】
    1. mPolicy->notifySwitch()
  • InputDispatcher分派输入事件(dispatchOnce)
  1. if (!haveCommandsLocked)()【命令队列没有命令】
    1. dispatchOnceInnerLocked()【处理一个事件】
      1. if (!mPolicy->isKeyRepeatEnabled)
        1. resetKeyRepeatLocked()【释放最近键击事件,mKeyRepeatState】
      2. if (!mPendingEvent)【如果没有等待事件,获取下一个事件】
        1. if (mInboundQueue.isEmpty)【如果没有排队事件,看看是否重复最近的键击事件】
          1. if (mKeyRepeatState.lastKeyEntry)
            1. if (currentTime >= mKeyRepeatState.nextRepeatTime)
              1. synthesizeKeyRepeatLocked【合成一个重复键击事件mPendingEvent】
          2. if (!mPendingEvent)【没有事件,返回】
            1. return
        2. else
          1. mInboundQueue.dequeueAtHead()【取队列开头mPendingEvent】
        3. if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)
          1. pokeUserActivityLocked()【??】
        4. resetANRTimeoutsLocked()【】
      3. switch (mPendingEvent->type)【根据事件类型,做相应处理】
        1. case EventEntry::TYPE_KEY【键击事件】
          1. dispatchKeyLocked()【分派键击事件】
            1. 【处理键击自动重复】
            2. 【策略要求稍后重试,return】
            3. 【策略在分派前干预,postCommandLocked】
            4. if (*dropReason != DROP_REASON_NOT_DROPPED)
              1. setInjectionResultLocked()【丢弃处理】
            5. findFocusedWindowTargetsLocked()【寻找焦点窗口】
            6. addMonitoringTargetsLocked()【添加监视目标管道mMonitoringChannels】
            7. dispatchEventLocked()【分派到各个目标,步骤七】
        2. case EventEntry::TYPE_MOTION【移动事件】
          1. dispatchMotionLocked()【分派移动事件】
            1. if (isPointerEvent)【触摸、点击事件】
              1. findTouchedWindowTargetsLocked()【寻找触摸窗口】
            2. else【其他,比如轨迹球】
              1. findFocusedWindowTargetsLocked()【寻找焦点窗口】
            3. if (isMainDisplay())
              1. addMonitoringTargetsLocked()【添加监视目标】
            4. dispatchEventLocked()【分派到各个目标,步骤七】
      4. if (done)
        1. if (dropReason != DROP_REASON_NOT_DROPPED)【事件要被丢弃】
          1. dropInboundEventLocked()【步骤六】】
        2. releasePendingEventLocked()【释放mPendingEvent】
  • InputDispatcher丢弃事件的处理(dropInboundEventLocked)
  1. synthesizeCancelationEventsForAllConnectionsLocked()
    1. foreach mConnectionsByFd()【每一个连接分别处理】
      1. synthesizeCancelationEventsForConnectionLocked()
        1. InputState::synthesizeCancelationEvents()【根据当前输入状态合成一系列取消事件DispatchEntry】
        2. foreach cancelationEvents
          1. enqueueDispatchEntryLocked()【插入连接的发送事件队列】
          2. startDispatchCycleLocked()【启动分派循环,步骤八】
  • InputDispatcher的分派事件(dispatchEventLocked)
  1. foreach inputTargets
    1. mConnectionsByFd.valueAt()【根据InputChannel找到内部Connection】
    2. prepareDispatchCycleLocked()
      1. splitMotionEvent()【分割移动事件,穿过窗口???】
      2. enqueueDispatchEntriesLocked()【一个事件EventEntry被拆成多个逻辑事件DispatchEntry】
        1. enqueueDispatchEntryLocked()【插入连接的发送事件队列】
        2. startDispatchCycleLocked()【启动分派循环,步骤八】
  • InputDispatcher的分派循环(startDispatchCycleLocked)
  1. while (!connection->outboundQueue.isEmpty())
    1. switch (eventEntry->type)
      1. case EventEntry::TYPE_KEY
        1. connection->inputPublisher.publishKeyEvent()
          1. mChannel->sendMessage()【InputChannel】
            1. ::send()【写入UNIX套接字】
      2. case EventEntry::TYPE_MOTION
        1. connection->inputPublisher.publishMotionEvent()
          1. mChannel->sendMessage
    2. if (status)
      1. if (status == WOULD_BLOCK)【套接字队列满】
        1. connection->inputPublisherBlocked = true
      2. else
        1. abortBrokenDispatchCycleLocked()
      3. return
    3. connection->outboundQueue.dequeue()【从发送队列删除】
    4. connection->waitQueue.enqueueAtTail()【加入等待应答队列】

 输入事件(Java层)

  • InputStage分发
  1. InputStage.deliver()
    1. if [已经处理或者丢弃]
      1. forward()
        1. mNext.deliver()
    2. else if [应该丢弃]
      1. finish(false)
        1. forward()
    3. else
      1. onProcess()
      2. apply(result)
        1. forward()
        2. finish(true/false)
  2. 输入事件分发策略:

类名

说明

NativePreImeInputStage

发给InputQueue异步处理(sendInputEvent),

继承InputQueue.FinishedInputEventCallback

目前只有RootViewSurfaceTaker派生类读取InputQueue

ViewPreImeInputStage

发给View树预处理(dispatchKeyEventPreIme)

ImeInputStage

发给InputMethodManager 异步处理(dispatchInputEvent),

继承InputMethodManager.FinishedInputEventCallback

EarlyPostImeInputStage

发给FallbackEventHandler处理(preDispatchKeyEvent)

NativePostImeInputStage

发给InputQueue异步处理(sendInputEvent),

继承InputQueue.FinishedInputEventCallback

ViewPostImeInputStage

发给View树处理(dispatchKeyEvent、

dispatchKeyShortcutEvent、dispatchUnhandledMove、

dispatchPointerEvent、dispatchTrackballEvent、

dispatchGenericMotionEvent)

SyntheticInputStage

合成事件,再排队处理

SyntheticTrackballHandler、SyntheticJoystickHandler、

SyntheticTouchNavigationHandler、SyntheticKeyHandler

 外部输入事件

  • InputManager处理(java,injectInputEvent)
  1. mIm.injectInputEvent(IInputManager)
    1. InputManagerService::injectInputEvent()
      1. Binder.clearCallingIdentity()【清除调用者身份,使用服务的权限】
      2. nativeInjectInputEvent()
        1. android_view_KeyEvent_toNative()
        2. android_view_MotionEvent_getNativePtr()
        3. im->getInputManager()->getDispatcher()->injectInputEvent()
  • InputDispatcher处理(C++,injectInputEvent)
  1. hasInjectionPermission()【如果有Injection,POLICY_FLAG_TRUSTED】
    1. mPolicy->checkInjectEventsPermissionNonReentrant()
  2. switch (event->getType())
    1. case AINPUT_EVENT_TYPE_KEY
      1. mPolicy->interceptKeyBeforeQueueing()
      2. new KeyEntry
    2. case AINPUT_EVENT_TYPE_MOTION
      1. mPolicy->interceptMotionBeforeQueueing()
      2. new MotionEntry
      3. for motionEvent->getHistorySize() ~ 1
        1. new MotionEntry
  3. new InjectionState【只赋给最后一个EventEntry】
  4. for firstInjectedEntry ~ NULL
    1. enqueueInboundEventLocked()【插入队列,见流程“输入事件”】
  5. 【等待同步InjectionState】
    1. if (INPUT_EVENT_INJECTION_SYNC_NONE)
      1. 【不等待】
    2. else
      1. 【等待INPUT_EVENT_INJECTION_PENDING改变】
        1. mInjectionResultAvailableCondition.waitRelative()
      2. if (INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED)
        1. 【等待pendingForegroundDispatches变为0】
          1. mInjectionSyncFinishedCondition.waitRelative()

外部设置布局

  • 调用InputManager设置某个设备的覆盖布局
  1. InputManager::setCurrentKeyboardLayoutForInputDevice()
    1. mIm.setCurrentKeyboardLayoutForInputDevice()【IInputManager,跨进程】
      1. mDataStore.setCurrentKeyboardLayout()【PersistentDataStore】
      2. mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS)
  • InputManagerService处理消息
  1. InputManagerService::handleMessage()
    1. case MSG_RELOAD_KEYBOARD_LAYOUTS
      1. reloadKeyboardLayouts()
        1. nativeReloadKeyboardLayouts()【调用JNI】
  • InputManagerService的JNI处理
  1. nativeReloadKeyboardLayouts()
    1. im->getInputManager()->getReader()->requestRefreshConfiguration()【CHANGE_KEYBOARD_LAYOUTS】
      1. mConfigurationChangesToRefresh【记录任务】
      2. mEventHub->wake()【唤醒线程处理】
  • InputReader的线程中处理配置更新
  1. InputReader::loopOnce()【线程循环】
    1. refreshConfigurationLocked()【如果mConfigurationChangesToRefresh】
      1. foreach mDevices
        1. device->configure()
          1. if (changes & CHANGE_KEYBOARD_LAYOUTS)
            1. mContext->getPolicy()->getKeyboardLayoutOverlay()【步骤五】
            2. mContext->getEventHub()->setKeyboardLayoutOverlay()
              1. KeyCharacterMap::combine()【合并map】
  • 反向调用,NativeInputManager读取布局
  1. NativeInputManager::getKeyboardLayoutOverlay()
    1. InputManagerService::getKeyboardLayoutOverlay()【方向调用java】
      1. getCurrentKeyboardLayoutForInputDevice()
        1. visitKeyboardLayout()【查找】
          1. KeyboardLayoutDescriptor.parse()
          2. pm.getReceiverInfo()【PackageManager】
          3. visitKeyboardLayoutsInPackage()
            1. pm.getResourcesForApplication()
    2. KeyCharacterMap::loadContents()

开始输入法

  • 调用InputManager设置某个设备的覆盖布局
  1. InputManager::setCurrentKeyboardLayoutForInputDevice()
    1. mIm.setCurrentKeyboardLayoutForInputDevice()【IInputManager,跨进程】
      1. mDataStore.setCurrentKeyboardLayout()【PersistentDataStore】
      2. mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS)
  2. InputMethodManagerService::startInput()
  • InputMethodManagerService开始输入法(startInput)
  1. startInputLocked()
    1. mClients.get()【获取ClientState】
    2. mIWindowManager.inputMethodClientHasFocus()【判断客户端是否有焦点】
    3. startInputUncheckedLocked()【IInputManager,跨进程】
      1. if (mCurClient != cs)【客户端改变了】
        1. isKeyguardLocked()【??】
        2. unbindCurrentClientLocked()
        3. executeOrSendMessage()【向新客户端发送MSG_SET_ACTIVE消息】
          1. if (target.asBinder() instanceof Binder)
            1. mCaller.sendMessage(msg)
          2. else【大部分情况走这个分支】
            1. handleMessage()
      2. mCurSeq++,切换mCurClient,mCurInputContext,mCurAttribute
      3. if (mCurId != null)
        1. if (cs.curSession != null)
          1. return attachNewInputLocked()
            1. if (!mBoundToMethod)
              1. executeOrSendMessage(mCurMethod, MSG_BIND_INPUT)
            2. executeOrSendMessage(session.method, MSG_START_INPUT)
            3. if (mShowRequested)
              1. showCurrentInputLocked()
                1. executeOrSendMessage(mCurMethod, MSG_SHOW_SOFT_INPUT)
                2. bindCurrentInputMethodService()【Visible】
            4. return new InputBindResult()
      4. return startInputInnerLocked()
        1. mMethodMap.get()【获取InputMethodInfo】
        2. unbindCurrentMethodLocked()
          1. if (mVisibleBound)
            1. mContext.unbindService()
          2. if (mHaveConnection)
            1. mContext.unbindService()
          3. if (mCurToken != null)
            1. mWindowManagerService.saveLastInputMethodWindowForTransition()
          4. mIWindowManager.removeWindowToken()
          5. clearCurMethodLocked()
          6. executeOrSendMessage(mCurClient.client, MSG_UNBIND_METHOD)
        3. mCurIntent = new Intent()
        4. bindCurrentInputMethodService(BIND_NOT_VISIBLE)
        5. mCurToken = new Binder()
        6. mIWindowManager.addWindowToken()
        7. return new InputBindResult()
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fighting Horse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值