Support invoking key shortcuts using Control.

This enables Select All, Cut, Copy and Paste behavior in TextViews
and provides a general pattern for implementing additional keyboard
accelerators based on Control key shortcuts.  The same shortcuts
also apply to menu accelerators.

Bug: 3286262
Change-Id: I7d458ee26abf51e0de1735ce490ce3baf504b471
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 97d7ad5..3a3d1d8 100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -990,6 +990,22 @@
      */
     public static final int META_SCROLL_LOCK_ON = 0x400000;
 
+    /** {@hide} */
+    public static final int META_SHIFT_MASK = META_SHIFT_ON
+            | META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON;
+
+    /** {@hide} */
+    public static final int META_ALT_MASK = META_ALT_ON
+            | META_ALT_LEFT_ON | META_ALT_RIGHT_ON;
+
+    /** {@hide} */
+    public static final int META_CTRL_MASK = META_CTRL_ON
+            | META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON;
+
+    /** {@hide} */
+    public static final int META_META_MASK = META_ALT_ON
+            | META_META_LEFT_ON | META_META_RIGHT_ON;
+
     /**
      * This mask is set if the device woke because of this key event.
      */
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 281dd27..5d81702 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -2626,6 +2626,16 @@
             return;
         }
 
+        // If the Control modifier is held, try to interpret the key as a shortcut.
+        if (event.getAction() == KeyEvent.ACTION_UP
+                && event.isCtrlPressed()
+                && !KeyEvent.isModifierKey(event.getKeyCode())) {
+            if (mView.dispatchKeyShortcutEvent(event)) {
+                finishKeyEvent(event, sendDone, true);
+                return;
+            }
+        }
+
         // Apply the fallback event policy.
         if (mFallbackEventHandler.dispatchKeyEvent(event)) {
             finishKeyEvent(event, sendDone, true);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 0699ac2..aa2e68f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7530,36 +7530,31 @@
 
     @Override
     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
-        switch (keyCode) {
-        case KeyEvent.KEYCODE_A:
-            if (canSelectText()) {
-                return onTextContextMenuItem(ID_SELECT_ALL);
+        final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
+        if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
+            switch (keyCode) {
+            case KeyEvent.KEYCODE_A:
+                if (canSelectText()) {
+                    return onTextContextMenuItem(ID_SELECT_ALL);
+                }
+                break;
+            case KeyEvent.KEYCODE_X:
+                if (canCut()) {
+                    return onTextContextMenuItem(ID_CUT);
+                }
+                break;
+            case KeyEvent.KEYCODE_C:
+                if (canCopy()) {
+                    return onTextContextMenuItem(ID_COPY);
+                }
+                break;
+            case KeyEvent.KEYCODE_V:
+                if (canPaste()) {
+                    return onTextContextMenuItem(ID_PASTE);
+                }
+                break;
             }
-
-            break;
-
-        case KeyEvent.KEYCODE_X:
-            if (canCut()) {
-                return onTextContextMenuItem(ID_CUT);
-            }
-
-            break;
-
-        case KeyEvent.KEYCODE_C:
-            if (canCopy()) {
-                return onTextContextMenuItem(ID_COPY);
-            }
-
-            break;
-
-        case KeyEvent.KEYCODE_V:
-            if (canPaste()) {
-                return onTextContextMenuItem(ID_PASTE);
-            }
-
-            break;
         }
-
         return super.onKeyShortcut(keyCode, event);
     }
 
@@ -7889,7 +7884,9 @@
 
     /**
      * Called when a context menu option for the text view is selected.  Currently
-     * this will be {@link android.R.id#copyUrl} or {@link android.R.id#selectTextMode}.
+     * this will be {@link android.R.id#copyUrl}, {@link android.R.id#selectTextMode},
+     * {@link android.R.id#selectAll}, {@link android.R.id#paste}, {@link android.R.id#cut}
+     * or {@link android.R.id#copy}.
      */
     public boolean onTextContextMenuItem(int id) {
         int min = 0;
@@ -7934,8 +7931,32 @@
                     startSelectionActionMode();
                 }
                 return true;
-            }
 
+            case ID_SELECT_ALL:
+                selectAll();
+                // Update controller positions after selection change.
+                if (hasSelectionController()) {
+                    getSelectionController().show();
+                }
+                return true;
+
+            case ID_PASTE:
+                paste(min, max);
+                return true;
+
+            case ID_CUT:
+                setPrimaryClip(ClipData.newPlainText(null, null,
+                        mTransformed.subSequence(min, max)));
+                ((Editable) mText).delete(min, max);
+                stopSelectionActionMode();
+                return true;
+
+            case ID_COPY:
+                setPrimaryClip(ClipData.newPlainText(null, null,
+                        mTransformed.subSequence(min, max)));
+                stopSelectionActionMode();
+                return true;
+        }
         return false;
     }
 
@@ -8292,49 +8313,7 @@
                  mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
                 return true;
             }
-
-            final int itemId = item.getItemId();
-
-            if (itemId == ID_SELECT_ALL) {
-                selectAll();
-                // Update controller positions after selection change.
-                if (hasSelectionController()) {
-                    getSelectionController().show();
-                }
-                return true;
-            }
-
-            int min = 0;
-            int max = mText.length();
-
-            if (isFocused()) {
-                final int selStart = getSelectionStart();
-                final int selEnd = getSelectionEnd();
-
-                min = Math.max(0, Math.min(selStart, selEnd));
-                max = Math.max(0, Math.max(selStart, selEnd));
-            }
-
-            switch (item.getItemId()) {
-                case ID_PASTE:
-                    paste(min, max);
-                    return true;
-
-                case ID_CUT:
-                    setPrimaryClip(ClipData.newPlainText(null, null,
-                            mTransformed.subSequence(min, max)));
-                    ((Editable) mText).delete(min, max);
-                    stopSelectionActionMode();
-                    return true;
-
-                case ID_COPY:
-                    setPrimaryClip(ClipData.newPlainText(null, null,
-                            mTransformed.subSequence(min, max)));
-                    stopSelectionActionMode();
-                    return true;
-            }
-
-            return false;
+            return onTextContextMenuItem(item.getItemId());
         }
 
         @Override