Minor Alt-TAB / Recent Apps Dialog improvements.

Alt-TAB should have different semantics from the APP_SWITCH key
or long-press on HOME.  Accordingly, remove the fallback action
for Alt-TAB and initiate the task switching behavior directly
in the policy.

Modified RecentApplicationsDialog to be more precise about the
initial modifiers that it considers to be holding the dialog.

The dialog is now dismissed by a second press on the APP_SWITCH
key or by a second long press on HOME.

Change-Id: Idf4d803f51103819057cb655ff3b770b7729e4be
diff --git a/api/current.xml b/api/current.xml
index 1f3b5fd..acc7719 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -213061,6 +213061,17 @@
  visibility="public"
 >
 </method>
+<method name="getModifiers"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getNumber"
  return="char"
  abstract="false"
@@ -267777,7 +267788,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="t" type="T">
+<parameter name="arg0" type="T">
 </parameter>
 </method>
 </interface>
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 4320160..c282e4b 100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -1807,12 +1807,33 @@
      * @see #META_CAPS_LOCK_ON
      * @see #META_NUM_LOCK_ON
      * @see #META_SCROLL_LOCK_ON
+     * @see #getModifiers
      */
     public final int getMetaState() {
         return mMetaState;
     }
 
     /**
+     * Returns the state of the modifier keys.
+     * <p>
+     * For the purposes of this function, {@link #KEYCODE_CAPS_LOCK},
+     * {@link #KEYCODE_SCROLL_LOCK}, and {@link #KEYCODE_NUM_LOCK} are
+     * not considered modifier keys.  Consequently, this function specifically masks out
+     * {@link #META_CAPS_LOCK_ON}, {@link #META_SCROLL_LOCK_ON} and {@link #META_NUM_LOCK_ON}.
+     * </p><p>
+     * The value returned consists of the meta state (from {@link #getMetaState})
+     * normalized using {@link #normalizeMetaState(int)} and then masked with
+     * {@link #getModifierMetaStateMask} so that only valid modifier bits are retained.
+     * </p>
+     *
+     * @return An integer in which each bit set to 1 represents a pressed modifier key.
+     * @see #getMetaState
+     */
+    public final int getModifiers() {
+        return normalizeMetaState(mMetaState) & META_MODIFIER_MASK;
+    }
+
+    /**
      * Returns the flags for this key event.
      *
      * @see #FLAG_WOKE_HERE
diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm
index ef0a4e6..b5f6897 100644
--- a/data/keyboards/Generic.kcm
+++ b/data/keyboards/Generic.kcm
@@ -300,8 +300,7 @@
 key TAB {
     label:                              '\t'
     base:                               '\t'
-    ctrl:                               none
-    alt, meta:                          fallback APP_SWITCH
+    ctrl, alt, meta:                    none
 }
 
 key COMMA {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 44f55b3..ac38d0b 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -626,7 +626,7 @@
         }
 
         if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_DIALOG) {
-            showRecentAppsDialog(0);
+            showOrHideRecentAppsDialog(0, true /*dismissIfShown*/);
         } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_ACTIVITY) {
             try {
                 Intent intent = new Intent();
@@ -643,16 +643,24 @@
     }
 
     /**
-     * Create (if necessary) and launch the recent apps dialog
+     * Create (if necessary) and launch the recent apps dialog, or hide it if it is
+     * already shown.
      */
-    void showRecentAppsDialog(final int initialModifiers) {
+    void showOrHideRecentAppsDialog(final int heldModifiers, final boolean dismissIfShown) {
         mHandler.post(new Runnable() {
             @Override
             public void run() {
                 if (mRecentAppsDialog == null) {
-                    mRecentAppsDialog = new RecentApplicationsDialog(mContext, initialModifiers);
+                    mRecentAppsDialog = new RecentApplicationsDialog(mContext);
                 }
-                mRecentAppsDialog.show();
+                if (mRecentAppsDialog.isShowing()) {
+                    if (dismissIfShown) {
+                        mRecentAppsDialog.dismiss();
+                    }
+                } else {
+                    mRecentAppsDialog.setHeldModifiers(heldModifiers);
+                    mRecentAppsDialog.show();
+                }
             }
         });
     }
@@ -1392,7 +1400,7 @@
             return false;
         } else if (keyCode == KeyEvent.KEYCODE_APP_SWITCH) {
             if (down && repeatCount == 0) {
-                showRecentAppsDialog(event.getMetaState() & KeyEvent.getModifierMetaStateMask());
+                showOrHideRecentAppsDialog(0, true /*dismissIfShown*/);
             }
             return true;
         }
@@ -1434,6 +1442,7 @@
     /** {@inheritDoc} */
     @Override
     public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
+        // Note: This method is only called if the initial down was unhandled.
         if (DEBUG_FALLBACK) {
             Slog.d(TAG, "Unhandled key: win=" + win + ", action=" + event.getAction()
                     + ", flags=" + event.getFlags()
@@ -1445,28 +1454,44 @@
         }
 
         if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
-            // Invoke shortcuts using Meta as a fallback.
             final KeyCharacterMap kcm = event.getKeyCharacterMap();
             final int keyCode = event.getKeyCode();
             final int metaState = event.getMetaState();
-            if ((metaState & KeyEvent.META_META_ON) != 0) {
-                Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode,
-                        metaState & ~(KeyEvent.META_META_ON
-                                | KeyEvent.META_META_LEFT_ON | KeyEvent.META_META_RIGHT_ON));
-                if (shortcutIntent != null) {
-                    shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                    try {
-                        mContext.startActivity(shortcutIntent);
-                    } catch (ActivityNotFoundException ex) {
-                        Slog.w(TAG, "Dropping shortcut key combination because "
-                                + "the activity to which it is registered was not found: "
-                                + "META+" + KeyEvent.keyCodeToString(keyCode), ex);
+            final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN
+                    && event.getRepeatCount() == 0;
+
+            if (initialDown) {
+                // Invoke shortcuts using Meta as a fallback.
+                if ((metaState & KeyEvent.META_META_ON) != 0) {
+                    Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode,
+                            metaState & ~(KeyEvent.META_META_ON
+                                    | KeyEvent.META_META_LEFT_ON | KeyEvent.META_META_RIGHT_ON));
+                    if (shortcutIntent != null) {
+                        shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                        try {
+                            mContext.startActivity(shortcutIntent);
+                        } catch (ActivityNotFoundException ex) {
+                            Slog.w(TAG, "Dropping shortcut key combination because "
+                                    + "the activity to which it is registered was not found: "
+                                    + "META+" + KeyEvent.keyCodeToString(keyCode), ex);
+                        }
+                        return null;
                     }
-                    return null;
+                }
+
+                // Display task switcher for ALT-TAB or Meta-TAB.
+                if (keyCode == KeyEvent.KEYCODE_TAB) {
+                    final int shiftlessModifiers = event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
+                    if (KeyEvent.metaStateHasModifiers(shiftlessModifiers, KeyEvent.META_ALT_ON)
+                            || KeyEvent.metaStateHasModifiers(
+                                    shiftlessModifiers, KeyEvent.META_META_ON)) {
+                        showOrHideRecentAppsDialog(shiftlessModifiers, false /*dismissIfShown*/);
+                        return null;
+                    }
                 }
             }
 
-            // Check for fallback actions.
+            // Check for fallback actions specified by the key character map.
             if (getFallbackAction(kcm, keyCode, metaState, mFallbackAction)) {
                 if (DEBUG_FALLBACK) {
                     Slog.d(TAG, "Fallback: keyCode=" + mFallbackAction.keyCode
diff --git a/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java b/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java
index c4b7822..aa00fbdd 100644
--- a/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java
+++ b/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java
@@ -71,12 +71,11 @@
         }
     };
 
-    private int mInitialModifiers;
+    private int mHeldModifiers;
 
-    public RecentApplicationsDialog(Context context, int initialModifiers) {
+    public RecentApplicationsDialog(Context context) {
         super(context, com.android.internal.R.style.Theme_Dialog_RecentApplications);
 
-        mInitialModifiers = initialModifiers;
     }
 
     /**
@@ -125,9 +124,20 @@
         }
     }
 
+    /**
+     * Sets the modifier keys that are being held to keep the dialog open, or 0 if none.
+     * Used to make the recent apps dialog automatically dismiss itself when the modifiers
+     * all go up.
+     * @param heldModifiers The held key modifiers, such as {@link KeyEvent#META_ALT_ON}.
+     * Should exclude shift.
+     */
+    public void setHeldModifiers(int heldModifiers) {
+        mHeldModifiers = heldModifiers;
+    }
+
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_APP_SWITCH || keyCode == KeyEvent.KEYCODE_TAB) {
+        if (keyCode == KeyEvent.KEYCODE_TAB) {
             // Ignore all meta keys other than SHIFT.  The app switch key could be a
             // fallback action chorded with ALT, META or even CTRL depending on the key map.
             // DPad navigation is handled by the ViewRoot elsewhere.
@@ -166,7 +176,7 @@
 
     @Override
     public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (mInitialModifiers != 0 && event.hasNoModifiers()) {
+        if (mHeldModifiers != 0 && (event.getModifiers() & mHeldModifiers) == 0) {
             final int numIcons = mIcons.length;
             RecentTag tag = null;
             for (int i = 0; i < numIcons; i++) {