Merge "Minor Alt-TAB / Recent Apps Dialog improvements."
diff --git a/api/current.xml b/api/current.xml
index 22d82ce..6749a564a 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -213125,6 +213125,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"
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 768c0cd..5f84547 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();
+                }
             }
         });
     }
@@ -1388,7 +1396,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;
         }
@@ -1430,6 +1438,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()
@@ -1441,28 +1450,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++) {