blob: ddcd60473097279c22efd88b06598ab3347c8e46 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show hashValues;
import 'package:flutter/foundation.dart';
import 'keyboard_key.dart';
import 'keyboard_maps.dart';
import 'raw_keyboard.dart';
// Android sets the 0x80000000 bit on a character to indicate that it is a
// combining character, so we use this mask to remove that bit to make it a
// valid Unicode character again.
const int _kCombiningCharacterMask = 0x7fffffff;
/// Platform-specific key event data for Android.
///
/// This object contains information about key events obtained from Android's
/// `KeyEvent` interface.
///
/// See also:
///
/// * [RawKeyboard], which uses this interface to expose key data.
class RawKeyEventDataAndroid extends RawKeyEventData {
/// Creates a key event data structure specific for Android.
///
/// The [flags], [codePoint], [keyCode], [scanCode], and [metaState] arguments
/// must not be null.
const RawKeyEventDataAndroid({
this.flags = 0,
this.codePoint = 0,
this.plainCodePoint = 0,
this.keyCode = 0,
this.scanCode = 0,
this.metaState = 0,
this.eventSource = 0,
this.vendorId = 0,
this.productId = 0,
this.deviceId = 0,
this.repeatCount = 0,
}) : assert(flags != null),
assert(codePoint != null),
assert(keyCode != null),
assert(scanCode != null),
assert(metaState != null);
/// The current set of additional flags for this event.
///
/// Flags indicate things like repeat state, etc.
///
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getFlags()>
/// for more information.
final int flags;
/// The Unicode code point represented by the key event, if any.
///
/// If there is no Unicode code point, this value is zero.
///
/// Dead keys are represented as Unicode combining characters.
///
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getUnicodeChar()>
/// for more information.
final int codePoint;
/// The Unicode code point represented by the key event, if any, without
/// regard to any modifier keys which are currently pressed.
///
/// If there is no Unicode code point, this value is zero.
///
/// Dead keys are represented as Unicode combining characters.
///
/// This is the result of calling KeyEvent.getUnicodeChar(0) on Android.
///
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getUnicodeChar(int)>
/// for more information.
final int plainCodePoint;
/// The hardware key code corresponding to this key event.
///
/// This is the physical key that was pressed, not the Unicode character.
/// See [codePoint] for the Unicode character.
///
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getKeyCode()>
/// for more information.
final int keyCode;
/// The hardware scan code id corresponding to this key event.
///
/// These values are not reliable and vary from device to device, so this
/// information is mainly useful for debugging.
///
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getScanCode()>
/// for more information.
final int scanCode;
/// The modifiers that were present when the key event occurred.
///
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getMetaState()>
/// for the numerical values of the `metaState`. Many of these constants are
/// also replicated as static constants in this class.
///
/// See also:
///
/// * [modifiersPressed], which returns a Map of currently pressed modifiers
/// and their keyboard side.
/// * [isModifierPressed], to see if a specific modifier is pressed.
/// * [isControlPressed], to see if a CTRL key is pressed.
/// * [isShiftPressed], to see if a SHIFT key is pressed.
/// * [isAltPressed], to see if an ALT key is pressed.
/// * [isMetaPressed], to see if a META key is pressed.
final int metaState;
/// The source of the event.
///
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getSource()>
/// for the numerical values of the `source`. Many of these constants are also
/// replicated as static constants in this class.
final int eventSource;
/// The vendor ID of the device that produced the event.
///
/// See <https://developer.android.com/reference/android/view/InputDevice.html#getVendorId()>
/// for the numerical values of the `vendorId`.
final int vendorId;
/// The product ID of the device that produced the event.
///
/// See <https://developer.android.com/reference/android/view/InputDevice.html#getProductId()>
/// for the numerical values of the `productId`.
final int productId;
/// The ID of the device that produced the event.
///
/// See https://developer.android.com/reference/android/view/InputDevice.html#getId()
final int deviceId;
/// The repeat count of the event.
///
/// See <https://developer.android.com/reference/android/view/KeyEvent#getRepeatCount()>
/// for more information.
final int repeatCount;
// The source code that indicates that an event came from a joystick.
// from https://developer.android.com/reference/android/view/InputDevice.html#SOURCE_JOYSTICK
static const int _sourceJoystick = 0x01000010;
// Android only reports a single code point for the key label.
@override
String get keyLabel => plainCodePoint == 0 ? '' : String.fromCharCode(plainCodePoint & _kCombiningCharacterMask);
@override
PhysicalKeyboardKey get physicalKey {
if (kAndroidToPhysicalKey.containsKey(scanCode)) {
return kAndroidToPhysicalKey[scanCode]!;
}
// Android sends DPAD_UP, etc. as the keyCode for joystick DPAD events, but
// it doesn't set the scanCode for those, so we have to detect this, and set
// our own DPAD physical keys. The logical key will still match "arrowUp",
// etc.
if (eventSource & _sourceJoystick == _sourceJoystick) {
final LogicalKeyboardKey? foundKey = kAndroidToLogicalKey[keyCode];
if (foundKey == LogicalKeyboardKey.arrowUp) {
return PhysicalKeyboardKey.arrowUp;
}
if (foundKey == LogicalKeyboardKey.arrowDown) {
return PhysicalKeyboardKey.arrowDown;
}
if (foundKey == LogicalKeyboardKey.arrowLeft) {
return PhysicalKeyboardKey.arrowLeft;
}
if (foundKey == LogicalKeyboardKey.arrowRight) {
return PhysicalKeyboardKey.arrowRight;
}
}
return PhysicalKeyboardKey(LogicalKeyboardKey.androidPlane + scanCode);
}
@override
LogicalKeyboardKey get logicalKey {
// Look to see if the keyCode is a printable number pad key, so that a
// difference between regular keys (e.g. "=") and the number pad version
// (e.g. the "=" on the number pad) can be determined.
final LogicalKeyboardKey? numPadKey = kAndroidNumPadMap[keyCode];
if (numPadKey != null) {
return numPadKey;
}
// If it has a non-control-character label, then either return the existing
// constant, or construct a new Unicode-based key from it. Don't mark it as
// autogenerated, since the label uniquely identifies an ID from the Unicode
// plane.
if (keyLabel.isNotEmpty && !LogicalKeyboardKey.isControlCharacter(keyLabel)) {
final int combinedCodePoint = plainCodePoint & _kCombiningCharacterMask;
final int keyId = LogicalKeyboardKey.unicodePlane | (combinedCodePoint & LogicalKeyboardKey.valueMask);
return LogicalKeyboardKey.findKeyByKeyId(keyId) ?? LogicalKeyboardKey(keyId);
}
// Look to see if the keyCode is one we know about and have a mapping for.
final LogicalKeyboardKey? newKey = kAndroidToLogicalKey[keyCode];
if (newKey != null) {
return newKey;
}
return LogicalKeyboardKey(keyCode | LogicalKeyboardKey.androidPlane);
}
bool _isLeftRightModifierPressed(KeyboardSide side, int anyMask, int leftMask, int rightMask) {
if (metaState & anyMask == 0) {
return false;
}
switch (side) {
case KeyboardSide.any:
return true;
case KeyboardSide.all:
return metaState & leftMask != 0 && metaState & rightMask != 0;
case KeyboardSide.left:
return metaState & leftMask != 0;
case KeyboardSide.right:
return metaState & rightMask != 0;
}
}
@override
bool isModifierPressed(ModifierKey key, { KeyboardSide side = KeyboardSide.any }) {
assert(side != null);
switch (key) {
case ModifierKey.controlModifier:
return _isLeftRightModifierPressed(side, modifierControl, modifierLeftControl, modifierRightControl);
case ModifierKey.shiftModifier:
return _isLeftRightModifierPressed(side, modifierShift, modifierLeftShift, modifierRightShift);
case ModifierKey.altModifier:
return _isLeftRightModifierPressed(side, modifierAlt, modifierLeftAlt, modifierRightAlt);
case ModifierKey.metaModifier:
return _isLeftRightModifierPressed(side, modifierMeta, modifierLeftMeta, modifierRightMeta);
case ModifierKey.capsLockModifier:
return metaState & modifierCapsLock != 0;
case ModifierKey.numLockModifier:
return metaState & modifierNumLock != 0;
case ModifierKey.scrollLockModifier:
return metaState & modifierScrollLock != 0;
case ModifierKey.functionModifier:
return metaState & modifierFunction != 0;
case ModifierKey.symbolModifier:
return metaState & modifierSym != 0;
}
}
@override
KeyboardSide? getModifierSide(ModifierKey key) {
KeyboardSide? findSide(int anyMask, int leftMask, int rightMask) {
final int combinedMask = leftMask | rightMask;
final int combined = metaState & combinedMask;
if (combined == leftMask) {
return KeyboardSide.left;
} else if (combined == rightMask) {
return KeyboardSide.right;
} else if (combined == combinedMask) {
return KeyboardSide.all;
}
// If the platform code sets the "any" modifier, but not a specific side,
// then we return "all", assuming that there is only one of that modifier
// key on the keyboard.
if (metaState & anyMask != 0) {
return KeyboardSide.all;
}
return null;
}
switch (key) {
case ModifierKey.controlModifier:
return findSide(modifierControl, modifierLeftControl, modifierRightControl);
case ModifierKey.shiftModifier:
return findSide(modifierShift, modifierLeftShift, modifierRightShift);
case ModifierKey.altModifier:
return findSide(modifierAlt, modifierLeftAlt, modifierRightAlt);
case ModifierKey.metaModifier:
return findSide(modifierMeta, modifierLeftMeta, modifierRightMeta);
case ModifierKey.capsLockModifier:
case ModifierKey.numLockModifier:
case ModifierKey.scrollLockModifier:
case ModifierKey.functionModifier:
case ModifierKey.symbolModifier:
return KeyboardSide.all;
}
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<int>('flags', flags));
properties.add(DiagnosticsProperty<int>('codePoint', codePoint));
properties.add(DiagnosticsProperty<int>('plainCodePoint', plainCodePoint));
properties.add(DiagnosticsProperty<int>('keyCode', keyCode));
properties.add(DiagnosticsProperty<int>('scanCode', scanCode));
properties.add(DiagnosticsProperty<int>('metaState', metaState));
}
@override
bool operator==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is RawKeyEventDataAndroid
&& other.flags == flags
&& other.codePoint == codePoint
&& other.plainCodePoint == plainCodePoint
&& other.keyCode == keyCode
&& other.scanCode == scanCode
&& other.metaState == metaState;
}
@override
int get hashCode => hashValues(
flags,
codePoint,
plainCodePoint,
keyCode,
scanCode,
metaState,
);
// Modifier key masks.
/// No modifier keys are pressed in the [metaState] field.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierNone = 0;
/// This mask is used to check the [metaState] field to test whether one of
/// the ALT modifier keys is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierAlt = 0x02;
/// This mask is used to check the [metaState] field to test whether the left
/// ALT modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierLeftAlt = 0x10;
/// This mask is used to check the [metaState] field to test whether the right
/// ALT modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierRightAlt = 0x20;
/// This mask is used to check the [metaState] field to test whether one of
/// the SHIFT modifier keys is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierShift = 0x01;
/// This mask is used to check the [metaState] field to test whether the left
/// SHIFT modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierLeftShift = 0x40;
/// This mask is used to check the [metaState] field to test whether the right
/// SHIFT modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierRightShift = 0x80;
/// This mask is used to check the [metaState] field to test whether the SYM
/// modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierSym = 0x04;
/// This mask is used to check the [metaState] field to test whether the
/// Function modifier key (Fn) is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierFunction = 0x08;
/// This mask is used to check the [metaState] field to test whether one of
/// the CTRL modifier keys is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierControl = 0x1000;
/// This mask is used to check the [metaState] field to test whether the left
/// CTRL modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierLeftControl = 0x2000;
/// This mask is used to check the [metaState] field to test whether the right
/// CTRL modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierRightControl = 0x4000;
/// This mask is used to check the [metaState] field to test whether one of
/// the META modifier keys is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierMeta = 0x10000;
/// This mask is used to check the [metaState] field to test whether the left
/// META modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierLeftMeta = 0x20000;
/// This mask is used to check the [metaState] field to test whether the right
/// META modifier key is pressed.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierRightMeta = 0x40000;
/// This mask is used to check the [metaState] field to test whether the CAPS
/// LOCK modifier key is on.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierCapsLock = 0x100000;
/// This mask is used to check the [metaState] field to test whether the NUM
/// LOCK modifier key is on.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierNumLock = 0x200000;
/// This mask is used to check the [metaState] field to test whether the
/// SCROLL LOCK modifier key is on.
///
/// Use this value if you need to decode the [metaState] field yourself, but
/// it's much easier to use [isModifierPressed] if you just want to know if
/// a modifier is pressed.
static const int modifierScrollLock = 0x400000;
}