Take the input device into account for meta state

Interesting nits about this:
- getMetaState(long) and getMetaState(long, int) do not
  seem to be used anywhere in the framework. Maybe we
  want to skip adding getMetaState(long, KeyEvent) and
  getMetaState(long, int, KeyEvent).
- getMetaState(CharSequence, int) is used extensively
  throughout the code. In some places, a KeyEvent is
  available and should be considered - this change does
  implement this. In many other cases, there is no
  relevant KeyEvent for this action (for example,
  Editor#extractTextInternal). In these cases, the
  behavior is affected by the current latch state, so
  it's fine to leave them as is.

Bug: 8303489

Change-Id: I4a948c497c49e48f83ba1048520e7fe5bfe84727
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 30bb447..ba6f1d4 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -56,7 +56,7 @@
                     if (event.getAction() == KeyEvent.ACTION_DOWN
                             && event.getRepeatCount() == 0
                             && MetaKeyKeyListener.getMetaState(buffer,
-                                        MetaKeyKeyListener.META_SELECTING) != 0) {
+                                        MetaKeyKeyListener.META_SELECTING, event) != 0) {
                         return widget.showContextMenu();
                     }
                 }
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index 4fede32..63607fa 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -75,7 +75,7 @@
         }
 
         // Alt+Backspace or Alt+ForwardDelete deletes the current line, if possible.
-        if (event.isAltPressed() || getMetaState(content, META_ALT_ON) == 1) {
+        if (getMetaState(content, META_ALT_ON, event) == 1) {
             if (deleteLine(view, content)) {
                 return true;
             }
diff --git a/core/java/android/text/method/BaseMovementMethod.java b/core/java/android/text/method/BaseMovementMethod.java
index 113a4be..155a2c4 100644
--- a/core/java/android/text/method/BaseMovementMethod.java
+++ b/core/java/android/text/method/BaseMovementMethod.java
@@ -135,7 +135,7 @@
      */
     protected int getMovementMetaState(Spannable buffer, KeyEvent event) {
         // We ignore locked modifiers and SHIFT.
-        int metaState = (event.getMetaState() | MetaKeyKeyListener.getMetaState(buffer))
+        int metaState = MetaKeyKeyListener.getMetaState(buffer, event)
                 & ~(MetaKeyKeyListener.META_ALT_LOCKED | MetaKeyKeyListener.META_SYM_LOCKED);
         return KeyEvent.normalizeMetaState(metaState) & ~KeyEvent.META_SHIFT_MASK;
     }
diff --git a/core/java/android/text/method/DialerKeyListener.java b/core/java/android/text/method/DialerKeyListener.java
index ce51fae..bb8b0de 100644
--- a/core/java/android/text/method/DialerKeyListener.java
+++ b/core/java/android/text/method/DialerKeyListener.java
@@ -53,7 +53,7 @@
      * from the KeyEvent.
      */
     protected int lookup(KeyEvent event, Spannable content) {
-        int meta = event.getMetaState() | getMetaState(content);
+        int meta = getMetaState(content, event);
         int number = event.getNumber();
 
         /*
diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java
index 5ebb957..e9db5fd 100644
--- a/core/java/android/text/method/MetaKeyKeyListener.java
+++ b/core/java/android/text/method/MetaKeyKeyListener.java
@@ -163,6 +163,29 @@
                getActive(text, SELECTING, META_SELECTING, META_SELECTING);
     }
 
+    /**
+     * Gets the state of the meta keys for a specific key event.
+     *
+     * For input devices that use toggled key modifiers, the `toggled' state
+     * is stored into the text buffer. This method retrieves the meta state
+     * for this event, accounting for the stored state. If the event has been
+     * created by a device that does not support toggled key modifiers, like
+     * a virtual device for example, the stored state is ignored.
+     *
+     * @param text the buffer in which the meta key would have been pressed.
+     * @param event the event for which to evaluate the meta state.
+     * @return an integer in which each bit set to one represents a pressed
+     *         or locked meta key.
+     */
+    public static final int getMetaState(final CharSequence text, final KeyEvent event) {
+        int metaState = event.getMetaState();
+        if (event.getKeyCharacterMap().getModifierBehavior()
+                == KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED) {
+            metaState |= getMetaState(text);
+        }
+        return metaState;
+    }
+
     // As META_SELECTING is @hide we should not mention it in public comments, hence the
     // omission in @param meta
     /**
@@ -192,6 +215,37 @@
         }
     }
 
+    /**
+     * Gets the state of a particular meta key to use with a particular key event.
+     *
+     * If the key event has been created by a device that does not support toggled
+     * key modifiers, like a virtual keyboard for example, only the meta state in
+     * the key event is considered.
+     *
+     * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON
+     * @param text the buffer in which the meta key would have been pressed.
+     * @param event the event for which to evaluate the meta state.
+     * @return 0 if inactive, 1 if active, 2 if locked.
+     */
+    public static final int getMetaState(final CharSequence text, final int meta,
+            final KeyEvent event) {
+        int metaState = event.getMetaState();
+        if (event.getKeyCharacterMap().getModifierBehavior()
+                == KeyCharacterMap.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED) {
+            metaState |= getMetaState(text);
+        }
+        if (META_SELECTING == meta) {
+            // #getMetaState(long, int) does not support META_SELECTING, but we want the same
+            // behavior as #getMetaState(CharSequence, int) so we need to do it here
+            if ((metaState & META_SELECTING) != 0) {
+                // META_SELECTING is only ever set to PRESSED and can't be LOCKED, so return 1
+                return 1;
+            }
+            return 0;
+        }
+        return getMetaState(metaState, meta);
+    }
+
     private static int getActive(CharSequence text, Object meta,
                                  int on, int lock) {
         if (!(text instanceof Spanned)) {
diff --git a/core/java/android/text/method/NumberKeyListener.java b/core/java/android/text/method/NumberKeyListener.java
index 5d4c732..988d566 100644
--- a/core/java/android/text/method/NumberKeyListener.java
+++ b/core/java/android/text/method/NumberKeyListener.java
@@ -41,7 +41,7 @@
     protected abstract char[] getAcceptedChars();
 
     protected int lookup(KeyEvent event, Spannable content) {
-        return event.getMatch(getAcceptedChars(), event.getMetaState() | getMetaState(content));
+        return event.getMatch(getAcceptedChars(), getMetaState(content, event));
     }
 
     public CharSequence filter(CharSequence source, int start, int end,
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
index 98316ae..0bd46bc 100644
--- a/core/java/android/text/method/QwertyKeyListener.java
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -108,7 +108,7 @@
 
         // QWERTY keyboard normal case
 
-        int i = event.getUnicodeChar(event.getMetaState() | getMetaState(content));
+        int i = event.getUnicodeChar(getMetaState(content, event));
 
         if (!mFullKeyboard) {
             int count = event.getRepeatCount();