欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android中外接键盘的检测的实现

程序员文章站 2022-05-31 12:43:10
今天来了一个问题:软键盘无法弹出。分析后是因为系统判断当前有外接硬键盘,就会隐藏软键盘。但实际情况并不是这么简单,该问题只有在特定条件下偶现,具体分析过程就不说了,就是软硬键盘支持上的...

今天来了一个问题:软键盘无法弹出。分析后是因为系统判断当前有外接硬键盘,就会隐藏软键盘。但实际情况并不是这么简单,该问题只有在特定条件下偶现,具体分析过程就不说了,就是软硬键盘支持上的逻辑问题。借着这个机会整理一下键盘检测的过程。

configuration

android系统中通过读取configuration中keyboard的值来判断是否存在外接键盘。configuration中关于键盘类型的定义如下,

  public static final int keyboard_undefined = 0; // 未定义的键盘
  public static final int keyboard_nokeys = 1; // 无键键盘,没有外接键盘时为该类型
  public static final int keyboard_qwerty = 2; // 标准外接键盘
  public static final int keyboard_12key = 3; // 12键小键盘

在最常见的情况下,外接键盘未连接时keyboard的值为keyboard_nokeys,当检测到键盘连接后会将keyboard的值更新为keyboard_qwerty 。应用就可以根据keyboard的值来判断是否存在外接键盘,inputmethodservice.java中有类似的判断代码。

  // 软件盘是否可以显示
  public boolean onevaluateinputviewshown() {
    configuration config = getresources().getconfiguration();
    return config.keyboard == configuration.keyboard_nokeys
        || config.hardkeyboardhidden == configuration.hardkeyboardhidden_yes;
  }  

现在的问题就转向configuration的keyboard是如何更新的。在windowmanagerservice.java中,应用启动时会更新configuration,相关代码如下。

  boolean computescreenconfigurationlocked(configuration config) {
    ......
    if (config != null) {
      // update the configuration based on available input devices, lid switch,
      // and platform configuration.
      config.touchscreen = configuration.touchscreen_notouch;
      // 默认值为keyboard_nokeys
      config.keyboard = configuration.keyboard_nokeys;
      config.navigation = configuration.navigation_nonav;
      
      int keyboardpresence = 0;
      int navigationpresence = 0;
      final inputdevice[] devices = minputmanager.getinputdevices();
      final int len = devices.length;
      // 遍历输入设备
      for (int i = 0; i < len; i++) {
        inputdevice device = devices[i];
        // 如果不是虚拟输入设备,会根据输入设备的flags来更新configuration
        if (!device.isvirtual()) {
          ......
          // 如果输入设备的键盘类型为keyboard_type_alphabetic,则将keyboard设置为keyboard_qwerty
          if (device.getkeyboardtype() == inputdevice.keyboard_type_alphabetic) {
            config.keyboard = configuration.keyboard_qwerty;
            keyboardpresence |= presenceflag;
          }
        }
      }
      ......
      // determine whether a hard keyboard is available and enabled.
      boolean hardkeyboardavailable = config.keyboard != configuration.keyboard_nokeys;
      // 更新硬件键盘状态
      if (hardkeyboardavailable != mhardkeyboardavailable) {
        mhardkeyboardavailable = hardkeyboardavailable;
        mh.removemessages(h.report_hard_keyboard_status_change);
        mh.sendemptymessage(h.report_hard_keyboard_status_change);
      }
      // 如果setting中show_ime_with_hard_keyboard被设置,将keyboard设置为keyboard_nokeys,让软件盘可以显示
      if (mshowimewithhardkeyboard) {
        config.keyboard = configuration.keyboard_nokeys;
      }
      ......
    }

影响configuration中keyboard的值有,

  • 默认值为keyboard_nokeys,表示没有外接键盘。
  • 当输入设备为keyboard_type_alphabetic时,更新为keyboard_qwerty,一个标准键盘。
  • 当settings.secure.show_ime_with_hard_keyboard为1时,设置为keyboard_nokeys,目的是让软键盘可以显示。

inputflinger

接下来需要关注输入设备时何时被设置keyboard_type_alphabetic的。搜索代码可以看到,这个flag实在native代码中设置的,代码在inputflinger/inputreader.cpp中。native和java使用了同一定义值,如果修改定义时需要注意同时修改。native中的名字为ainput_keyboard_type_alphabetic。

inputdevice* inputreader::createdevicelocked(int32_t deviceid, int32_t controllernumber,
    const inputdeviceidentifier& identifier, uint32_t classes) {
  inputdevice* device = new inputdevice(&mcontext, deviceid, bumpgenerationlocked(),
      controllernumber, identifier, classes);
  ......
  if (classes & input_device_class_alphakey) {
    keyboardtype = ainput_keyboard_type_alphabetic;
  }
  ......
  return device;
}

inputreader在增加设备时,根据classes的flag来设置键盘类型。这个flag又是在eventhub.cpp中设置的。

status_t eventhub::opendevicelocked(const char *devicepath) {
  ......
  // configure the keyboard, gamepad or virtual keyboard.
  if (device->classes & input_device_class_keyboard) { 
    // 'q' key support = cheap test of whether this is an alpha-capable kbd
    if (haskeycodelocked(device, akeycode_q)) {
      device->classes |= input_device_class_alphakey;
    }
  ......
}

看到这里就比较明确了,在eventhub加载设备时,如果输入设备为键盘,并且带有'q'键,就认为这是一个标准的外接键盘。但为何判断'q'键还不是很清楚。

keylayout

上面说道通过'q'键来判断是否为外接键盘,这个'q'键是android的键值,键值是否存在是通过一个keylayout文件决定的。kl文件存储在目标系统的/system/usr/keylayout/下,系统可以有多个kl文件,根据设备的id来命名。当系统加载键盘设备时,就会根据设备的vendor id和product id在/system/usr/keylayout/下寻找kl文件。例如一个kl文件名为”vendor_0c45_product_1109.kl“,表明设备的vendor id为0c45,product id为1109。一个kl的内容示例如下,

key 1   back
key 28  dpad_center
key 102  home

key 103  dpad_up
key 105  dpad_left
key 106  dpad_right
key 108  dpad_down

key 113  volume_mute
key 114  volume_down
key 115  volume_up

key 142  power

键值映射需要使用关键之”key“进行声明,之后跟着的数字为linux驱动中的键值定义,再后面的字符串是android中按键的名称。'q'键是否存在完全取决于kl文件中是否有映射,而不是实际物理键是否存在。kl文件的查找也是有一个规则的,其查找顺序如下,

/system/usr/keylayout/vendor_xxxx_product_xxxx_version_xxxx.kl
/system/usr/keylayout/vendor_xxxx_product_xxxx.kl

/system/usr/keylayout/device_name.kl

/data/system/devices/keylayout/vendor_xxxx_product_xxxx_version_xxxx.kl

/data/system/devices/keylayout/vendor_xxxx_product_xxxx.kl

/data/system/devices/keylayout/device_name.kl

/system/usr/keylayout/generic.kl

/data/system/devices/keylayout/generic.kl

同时支持软硬键盘

有了上面的知识,就可以给出同时支持软硬键盘的方案。

  • 修改源码逻辑,设置configuration中keyboard的值为keyboard_nokeys。这种hack其实不好,破坏原生逻辑,缺乏移植性。非要这样改的话,可以增加对设备的判断,只有特定的键盘设备设置为keyboard_nokeys,减少副作用。
  • 修改keylayout,去掉'q'键映射。有时kl文件写的不标准,为了通用把所有键的映射都写上了,实际硬件键却很少,我们就是这种情况。应该按照真实硬件来编写kl文件。
  • 设置settings.secure.show_ime_with_hard_keyboard为1。我认为这是最标准的修改方式,也非常方便。

关于第三个方案的修改方式有两种,一种是修改缺省的setting值,在文件frameworks/base/packages/settingsprovider/res/values/defaults.xml中增加,

<integer name="def_show_ime_with_hard_keyboard">1</integer>

另一种方式是在系统启动时在代码中通过接口进行设置。

settings.secure.putint(context.getcontentresolver(), settings.secure.show_ime_with_hard_keyboard, 1);

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。