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

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

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);

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

(0)

相关推荐

  • android 键盘事件和屏幕事件的运行原理及交互实现

    当在自定义View或者做游戏的时候,我们常常会用到键盘触发事件和屏幕触发事件!在自定义的View里的键盘触发事件(比如:onKeyDown(int keyCode, KeyEvent event))和屏幕触发事件(onTouchEvent(MotionEvent event))和activity里的键盘触发事件(比如:onKeyDown(int keyCode, KeyEvent event))和屏幕触发事件(onTouchEvent(MotionEvent event))是怎么样交互的呢?是怎

  • Android 显示和隐藏软键盘的方法(手动)

    在Android开发中,经常会有一个需求,做完某项操作后,隐藏键盘,也即让Android中的软键盘不显示.今天,和大家分享如何利用代码来实现对Android的软件盘的隐藏.显示的操作. 1.方法一(如果输入法在窗口上已经显示,则隐藏,反之则显示) InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.toggleSoftInput(0, InputMeth

  • Android实现弹出键盘的方法

    本文实例讲述了Android实现弹出键盘代码,是一个非常实用的功能.代码非常简洁.分享给大家供大家参考. 具体功能代码如下: Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { InputMethodManager m = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_

  • Android软键盘显示模式及打开和关闭方式(推荐)

    Android软键盘显示模式: Android定义了一个属性,名字为windowSoftInputMode, 用它可以让程序可以控制活动主窗口调整的方式.我们可以在AndroidManifet.xml中对Activity进行设置.如:android:windowSoftInputMode="stateUnchanged|adjustPan" 该属性可选的值有两部分,一部分为软键盘的状态控制,另一部分是活动主窗口的调整.前一部分本文不做讨论,请读者自行查阅android文档.     模

  • Android软键盘挡住输入框的终极解决方案

    前言 开发做得久了,总免不了会遇到各种坑. 而在Android开发的路上,『软键盘挡住了输入框』这个坑,可谓是一个旷日持久的巨坑--来来来,我们慢慢看. 入门篇 最基本的情况,如图所示:在页面底部有一个EditText,如果不做任何处理,那么在软键盘弹出的时候,就有可能会挡住EditText. 对于这种情况的处理其实很简单,只需要在AndroidManifest文件中对activity设置:android:windowSoftInputMode的值adjustPan或者adjustResize即

  • Android中监听软键盘显示状态实现代码

    /**监听软键盘状态 * @param activity * @param listener */ public static void addOnSoftKeyBoardVisibleListener(Activity activity, final OnSoftKeyBoardVisibleListener listener) { final View decorView = activity.getWindow().getDecorView(); decorView.getViewTree

  • 解析android中隐藏与显示软键盘及不自动弹出键盘的实现方法

    1.//隐藏软键盘    ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(WidgetSearchActivity.this.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); 2.//显示软键盘,控件ID可以是EditText,TextView    ((InputMethodMa

  • Android制作漂亮自适布局键盘的方法

    最近做了个自定义键盘,但面对不同分辨率的机型其中数字键盘不能根据界面大小自已铺满,但又不能每种机型都做一套吧,所以要做成自适应,那这里主讲思路. 这里最上面的titlebar高度固定,下面输入的金额高度也固定(当然也可以自适应),主要是中间的数字键盘,高度和宽度需要自适应.先来张效果图: 最常见的解决方案是用线性布局,自适应当然是按比例,但布局中无%的概念,那就要用到layout_weight了,该属性的作用是决定控件在其父布局中的显示权重(具体概念就不多说了). 这里用一个LinearLayo

  • Android 设置Edittext获取焦点并弹出软键盘

    Android 设置Edittext获取焦点并弹出软键盘 /** * EditText获取焦点并显示软键盘 */ public static void showSoftInputFromWindow(Activity activity, EditText editText) { editText.setFocusable(true); editText.setFocusableInTouchMode(true); editText.requestFocus(); activity.getWind

  • Android键盘显示与隐藏代码

    Java代码 复制代码 代码如下: InputMethodManager imm = (InputMethodManager)getSystemService(SendActivity.this.INPUT_METHOD_SERVICE); //显示键盘 imm.showSoftInput(editText, 0); //隐藏键盘 imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);

  • 浅析Android 模拟键盘鼠标事件

    通过Socket + Instrumentation实现模拟键盘鼠标事件主要通过以下三个部分组成:Socket编程:实现PC和Emulator通讯,并进行循环监听Service服务:将Socket的监听程序放在Service中,从而达到后台运行的目的.这里要说明的是启动服务有两种方式,bindService和startService,两者的区别是,前者会使启动的Service随着启动Service的Activity的消亡而消亡,而startService则不会这样,除非显式调用stopServi

随机推荐