Merge "Fix bug 5011824 - New Holo overflow menu for physical menu key devices"
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 81cd798..c7bf8e3 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -184,6 +184,13 @@
      */
     int watchRotation(IRotationWatcher watcher);
 
+    /**
+     * Determine the preferred edge of the screen to pin the compact options menu against.
+     * @return a Gravity value for the options menu panel
+     * @hide
+     */
+    int getPreferredOptionsPanelGravity();
+
 	/**
 	 * Lock the device orientation to the current rotation. Sensor input will
 	 * be ignored until thawRotation() is called.
diff --git a/core/res/res/layout/expanded_menu_layout.xml b/core/res/res/layout/expanded_menu_layout.xml
index 5d98773..f44a83f 100644
--- a/core/res/res/layout/expanded_menu_layout.xml
+++ b/core/res/res/layout/expanded_menu_layout.xml
@@ -16,5 +16,5 @@
 
 <com.android.internal.view.menu.ExpandedMenuView xmlns:android="http://schemas.android.com/apk/res/android"
 	android:id="@+android:id/expanded_menu" 
-	android:layout_width="296dip"
+	android:layout_width="?android:attr/panelMenuListWidth"
 	android:layout_height="wrap_content" />
diff --git a/core/res/res/layout/list_menu_item_layout.xml b/core/res/res/layout/list_menu_item_layout.xml
index 57091a1..aaff4c7 100644
--- a/core/res/res/layout/list_menu_item_layout.xml
+++ b/core/res/res/layout/list_menu_item_layout.xml
@@ -16,7 +16,7 @@
 
 <com.android.internal.view.menu.ListMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="?android:attr/listPreferredItemHeight">
+    android:layout_height="?android:attr/listPreferredItemHeightSmall">
     
     <!-- Icon will be inserted here. -->
     
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index f70319b..eed41ea 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -439,6 +439,10 @@
         <!-- Default appearance of panel text. -->
         <attr name="panelTextAppearance" format="reference" />
 
+        <attr name="panelMenuIsCompact" format="boolean" />
+        <attr name="panelMenuListWidth" format="dimension" />
+        <attr name="panelMenuListTheme" format="reference" />
+
         <!-- =================== -->
         <!-- Other widget styles -->
         <!-- =================== -->
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 615a37d..82299b8 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -185,6 +185,9 @@
         <item name="panelColorForeground">?android:attr/textColorPrimary</item>
         <item name="panelTextAppearance">?android:attr/textAppearance</item>
 
+        <item name="panelMenuIsCompact">false</item>
+        <item name="panelMenuListWidth">296dip</item>
+
         <!-- Scrollbar attributes -->
         <item name="scrollbarFadeDuration">250</item>
         <item name="scrollbarDefaultDelayBeforeFade">300</item> 
@@ -755,6 +758,22 @@
         <item name="android:background">@null</item>
     </style>
 
+    <style name="Theme.Holo.CompactMenu">
+        <!-- Menu/item attributes -->
+        <item name="android:itemTextAppearance">?android:attr/textAppearanceMedium</item>
+        <item name="android:listViewStyle">@android:style/Widget.Holo.ListView</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation.DropDownUp</item>
+        <item name="android:background">@null</item>
+    </style>
+
+    <style name="Theme.Holo.Light.CompactMenu">
+        <!-- Menu/item attributes -->
+        <item name="android:itemTextAppearance">?android:attr/textAppearanceMedium</item>
+        <item name="android:listViewStyle">@android:style/Widget.Holo.Light.ListView</item>
+        <item name="android:windowAnimationStyle">@android:style/Animation.DropDownUp</item>
+        <item name="android:background">@null</item>
+    </style>
+
     <!-- @hide -->
     <style name="Theme.Dialog.AppError" parent="Theme.Holo.Dialog">
         <item name="windowFrame">@null</item>
@@ -947,13 +966,17 @@
         <item name="toastFrameBackground">@android:drawable/toast_frame_holo</item>
         
         <!-- Panel attributes -->
-        <item name="panelBackground">@android:drawable/menu_background</item>
+        <item name="panelBackground">@android:drawable/menu_dropdown_panel_holo_dark</item>
         <item name="panelFullBackground">@android:drawable/menu_background_fill_parent_width</item>
         <!-- These three attributes do not seems to be used by the framework. Declared public though -->
         <item name="panelColorBackground">#000</item>
         <item name="panelColorForeground">?android:attr/textColorPrimary</item>
         <item name="panelTextAppearance">?android:attr/textAppearance</item>
 
+        <item name="panelMenuIsCompact">true</item>
+        <item name="panelMenuListWidth">250dip</item>
+        <item name="panelMenuListTheme">@android:style/Theme.Holo.CompactMenu</item>
+
         <!-- Scrollbar attributes -->
         <item name="scrollbarFadeDuration">250</item>
         <item name="scrollbarDefaultDelayBeforeFade">300</item> 
@@ -1244,13 +1267,17 @@
         <item name="toastFrameBackground">@android:drawable/toast_frame_holo</item>
         
         <!-- Panel attributes -->
-        <item name="panelBackground">@android:drawable/menu_background</item>
+        <item name="panelBackground">@android:drawable/menu_dropdown_panel_holo_light</item>
         <item name="panelFullBackground">@android:drawable/menu_background_fill_parent_width</item>
         <!-- These three attributes do not seems to be used by the framework. Declared public though -->
         <item name="panelColorBackground">#000</item>
         <item name="panelColorForeground">?android:attr/textColorPrimary</item>
         <item name="panelTextAppearance">?android:attr/textAppearance</item>
 
+        <item name="panelMenuIsCompact">true</item>
+        <item name="panelMenuListWidth">250dip</item>
+        <item name="panelMenuListTheme">@android:style/Theme.Holo.Light.CompactMenu</item>
+
         <!-- Scrollbar attributes -->
         <item name="scrollbarFadeDuration">250</item>
         <item name="scrollbarDefaultDelayBeforeFade">300</item>
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 14f7c11..6dd4948 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -51,8 +51,11 @@
 import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.util.AndroidRuntimeException;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
@@ -61,6 +64,8 @@
 import android.util.TypedValue;
 import android.view.ActionMode;
 import android.view.Gravity;
+import android.view.IRotationWatcher;
+import android.view.IWindowManager;
 import android.view.InputQueue;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
@@ -85,6 +90,9 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
 /**
  * Android-specific Window.
  * <p>
@@ -177,6 +185,13 @@
 
     private int mUiOptions = 0;
 
+    static class WindowManagerHolder {
+        static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface(
+                ServiceManager.getService("window"));
+    }
+
+    static final RotationWatcher sRotationWatcher = new RotationWatcher();
+
     public PhoneWindow(Context context) {
         super(context);
         mLayoutInflater = LayoutInflater.from(context);
@@ -415,8 +430,8 @@
                     if (st.iconMenuPresenter != null) {
                         st.iconMenuPresenter.saveHierarchyState(state);
                     }
-                    if (st.expandedMenuPresenter != null) {
-                        st.expandedMenuPresenter.saveHierarchyState(state);
+                    if (st.listMenuPresenter != null) {
+                        st.listMenuPresenter.saveHierarchyState(state);
                     }
 
                     // Remove the menu views since they need to be recreated
@@ -430,8 +445,8 @@
                     if (st.iconMenuPresenter != null) {
                         st.iconMenuPresenter.restoreHierarchyState(state);
                     }
-                    if (st.expandedMenuPresenter != null) {
-                        st.expandedMenuPresenter.restoreHierarchyState(state);
+                    if (st.listMenuPresenter != null) {
+                        st.listMenuPresenter.restoreHierarchyState(state);
                     }
 
                 } else {
@@ -552,7 +567,7 @@
             if (!st.shownPanelView.hasFocus()) {
                 st.shownPanelView.requestFocus();
             }
-        } else if (!st.isInExpandedMode) {
+        } else if (!st.isInListMode()) {
             width = MATCH_PARENT;
         }
 
@@ -567,7 +582,13 @@
                 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
                 st.decorView.mDefaultOpacity);
 
-        lp.gravity = st.gravity;
+        if (st.isCompact) {
+            lp.gravity = getOptionsPanelGravity();
+            sRotationWatcher.addWindow(this);
+        } else {
+            lp.gravity = st.gravity;
+        }
+
         lp.windowAnimations = st.windowAnimations;
         
         wm.addView(st.decorView, lp);
@@ -610,6 +631,9 @@
             if (st.decorView != null) {
                 wm.removeView(st.decorView);
                 // Log.v(TAG, "Removing main menu from window manager.");
+                if (st.isCompact) {
+                    sRotationWatcher.removeWindow(this);
+                }
             }
 
             if (doCallback) {
@@ -961,6 +985,36 @@
     }
 
     /**
+     * Determine the gravity value for the options panel. This can
+     * differ in compact mode.
+     *
+     * @return gravity value to use for the panel window
+     */
+    private int getOptionsPanelGravity() {
+        try {
+            return WindowManagerHolder.sWindowManager.getPreferredOptionsPanelGravity();
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Couldn't getOptionsPanelGravity; using default", ex);
+            return Gravity.CENTER | Gravity.BOTTOM;
+        }
+    }
+
+    void onOptionsPanelRotationChanged() {
+        final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+        if (st == null) return;
+
+        final WindowManager.LayoutParams lp = st.decorView != null ?
+                (WindowManager.LayoutParams) st.decorView.getLayoutParams() : null;
+        if (lp != null) {
+            lp.gravity = getOptionsPanelGravity();
+            final ViewManager wm = getWindowManager();
+            if (wm != null) {
+                wm.updateViewLayout(st.decorView, lp);
+            }
+        }
+    }
+
+    /**
      * Initializes the panel associated with the panel feature state. You must
      * at the very least set PanelFeatureState.panel to the View implementing
      * its contents. The default implementation gets the panel from the menu.
@@ -982,8 +1036,8 @@
             mPanelMenuPresenterCallback = new PanelMenuPresenterCallback();
         }
 
-        MenuView menuView = st.isInExpandedMode
-                ? st.getExpandedMenuView(mPanelMenuPresenterCallback)
+        MenuView menuView = st.isInListMode()
+                ? st.getListMenuView(mPanelMenuPresenterCallback)
                 : st.getIconMenuView(mPanelMenuPresenterCallback);
 
         st.shownPanelView = (View) menuView;
@@ -3015,7 +3069,13 @@
         MenuBuilder menu;
 
         IconMenuPresenter iconMenuPresenter;
-        ListMenuPresenter expandedMenuPresenter;
+        ListMenuPresenter listMenuPresenter;
+
+        /** true if this menu will show in single-list compact mode */
+        boolean isCompact;
+
+        /** Theme resource ID for list elements of the panel menu */
+        int listPresenterTheme;
 
         /**
          * Whether the panel has been prepared (see
@@ -3065,11 +3125,15 @@
             refreshDecorView = false;
         }
 
+        public boolean isInListMode() {
+            return isInExpandedMode || isCompact;
+        }
+
         public boolean hasPanelItems() {
             if (shownPanelView == null) return false;
 
-            if (isInExpandedMode) {
-                return expandedMenuPresenter.getAdapter().getCount() > 0;
+            if (isCompact || isInExpandedMode) {
+                return listMenuPresenter.getAdapter().getCount() > 0;
             } else {
                 return ((ViewGroup) shownPanelView).getChildCount() > 0;
             }
@@ -3081,10 +3145,10 @@
         public void clearMenuPresenters() {
             if (menu != null) {
                 menu.removeMenuPresenter(iconMenuPresenter);
-                menu.removeMenuPresenter(expandedMenuPresenter);
+                menu.removeMenuPresenter(listMenuPresenter);
             }
             iconMenuPresenter = null;
-            expandedMenuPresenter = null;
+            listMenuPresenter = null;
         }
 
         void setStyle(Context context) {
@@ -3095,6 +3159,11 @@
                     com.android.internal.R.styleable.Theme_panelFullBackground, 0);
             windowAnimations = a.getResourceId(
                     com.android.internal.R.styleable.Theme_windowAnimationStyle, 0);
+            isCompact = a.getBoolean(
+                    com.android.internal.R.styleable.Theme_panelMenuIsCompact, false);
+            listPresenterTheme = a.getResourceId(
+                    com.android.internal.R.styleable.Theme_panelMenuListTheme,
+                    com.android.internal.R.style.Theme_ExpandedMenu);
             a.recycle();
         }
 
@@ -3102,22 +3171,26 @@
             this.menu = menu;
         }
 
-        MenuView getExpandedMenuView(MenuPresenter.Callback cb) {
+        MenuView getListMenuView(MenuPresenter.Callback cb) {
             if (menu == null) return null;
 
-            getIconMenuView(cb); // Need this initialized to know where our offset goes
-
-            if (expandedMenuPresenter == null) {
-                expandedMenuPresenter = new ListMenuPresenter(
-                        com.android.internal.R.layout.list_menu_item_layout,
-                        com.android.internal.R.style.Theme_ExpandedMenu);
-                expandedMenuPresenter.setCallback(cb);
-                expandedMenuPresenter.setId(com.android.internal.R.id.list_menu_presenter);
-                menu.addMenuPresenter(expandedMenuPresenter);
+            if (!isCompact) {
+                getIconMenuView(cb); // Need this initialized to know where our offset goes
             }
 
-            expandedMenuPresenter.setItemIndexOffset(iconMenuPresenter.getNumActualItemsShown());
-            MenuView result = expandedMenuPresenter.getMenuView(decorView);
+            if (listMenuPresenter == null) {
+                listMenuPresenter = new ListMenuPresenter(
+                        com.android.internal.R.layout.list_menu_item_layout, listPresenterTheme);
+                listMenuPresenter.setCallback(cb);
+                listMenuPresenter.setId(com.android.internal.R.id.list_menu_presenter);
+                menu.addMenuPresenter(listMenuPresenter);
+            }
+
+            if (iconMenuPresenter != null) {
+                listMenuPresenter.setItemIndexOffset(
+                        iconMenuPresenter.getNumActualItemsShown());
+            }
+            MenuView result = listMenuPresenter.getMenuView(decorView);
 
             return result;
         }
@@ -3224,6 +3297,69 @@
 
     }
 
+    static class RotationWatcher extends IRotationWatcher.Stub {
+        private Handler mHandler;
+        private final Runnable mRotationChanged = new Runnable() {
+            public void run() {
+                dispatchRotationChanged();
+            }
+        };
+        private final ArrayList<WeakReference<PhoneWindow>> mWindows =
+                new ArrayList<WeakReference<PhoneWindow>>();
+        private boolean mIsWatching;
+
+        @Override
+        public void onRotationChanged(int rotation) throws RemoteException {
+            mHandler.post(mRotationChanged);
+        }
+
+        public void addWindow(PhoneWindow phoneWindow) {
+            synchronized (mWindows) {
+                if (!mIsWatching) {
+                    try {
+                        WindowManagerHolder.sWindowManager.watchRotation(this);
+                        mHandler = new Handler();
+                        mIsWatching = true;
+                    } catch (RemoteException ex) {
+                        Log.e(TAG, "Couldn't start watching for device rotation", ex);
+                    }
+                }
+                mWindows.add(new WeakReference<PhoneWindow>(phoneWindow));
+            }
+        }
+
+        public void removeWindow(PhoneWindow phoneWindow) {
+            synchronized (mWindows) {
+                int i = 0;
+                while (i < mWindows.size()) {
+                    final WeakReference<PhoneWindow> ref = mWindows.get(i);
+                    final PhoneWindow win = ref.get();
+                    if (win == null || win == phoneWindow) {
+                        mWindows.remove(i);
+                    } else {
+                        i++;
+                    }
+                }
+            }
+        }
+
+        void dispatchRotationChanged() {
+            synchronized (mWindows) {
+                int i = 0;
+                while (i < mWindows.size()) {
+                    final WeakReference<PhoneWindow> ref = mWindows.get(i);
+                    final PhoneWindow win = ref.get();
+                    if (win != null) {
+                        win.onOptionsPanelRotationChanged();
+                        i++;
+                    } else {
+                        mWindows.remove(i);
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Simple implementation of MenuBuilder.Callback that:
      * <li> Opens a submenu when selected.
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index e0b5e17..c07531e 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -95,6 +95,7 @@
 import android.util.SparseIntArray;
 import android.util.TypedValue;
 import android.view.Display;
+import android.view.Gravity;
 import android.view.IApplicationToken;
 import android.view.IOnKeyguardExitResult;
 import android.view.IRotationWatcher;
@@ -5163,6 +5164,57 @@
     }
 
     /**
+     * Apps that use the compact menu panel (as controlled by the panelMenuIsCompact
+     * theme attribute) on devices that feature a physical options menu key attempt to position
+     * their menu panel window along the edge of the screen nearest the physical menu key.
+     * This lowers the travel distance between invoking the menu panel and selecting
+     * a menu option.
+     *
+     * This method helps control where that menu is placed. Its current implementation makes
+     * assumptions about the menu key and its relationship to the screen based on whether
+     * the device's natural orientation is portrait (width < height) or landscape.
+     *
+     * The menu key is assumed to be located along the bottom edge of natural-portrait
+     * devices and along the right edge of natural-landscape devices. If these assumptions
+     * do not hold for the target device, this method should be changed to reflect that.
+     *
+     * @return A {@link Gravity} value for placing the options menu window
+     */
+    public int getPreferredOptionsPanelGravity() {
+        synchronized (mWindowMap) {
+            final int rotation = getRotation();
+
+            if (mInitialDisplayWidth < mInitialDisplayHeight) {
+                // On devices with a natural orientation of portrait
+                switch (rotation) {
+                    default:
+                    case Surface.ROTATION_0:
+                        return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+                    case Surface.ROTATION_90:
+                        return Gravity.RIGHT | Gravity.CENTER_VERTICAL;
+                    case Surface.ROTATION_180:
+                        return Gravity.CENTER_HORIZONTAL | Gravity.TOP;
+                    case Surface.ROTATION_270:
+                        return Gravity.LEFT | Gravity.CENTER_VERTICAL;
+                }
+            } else {
+                // On devices with a natural orientation of landscape
+                switch (rotation) {
+                    default:
+                    case Surface.ROTATION_0:
+                        return Gravity.RIGHT | Gravity.CENTER_VERTICAL;
+                    case Surface.ROTATION_90:
+                        return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+                    case Surface.ROTATION_180:
+                        return Gravity.LEFT | Gravity.CENTER_VERTICAL;
+                    case Surface.ROTATION_270:
+                        return Gravity.CENTER_HORIZONTAL | Gravity.TOP;
+                }
+            }
+        }
+    }
+
+    /**
      * Starts the view server on the specified port.
      *
      * @param port The port to listener to.
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java
index d94f369..5952c37 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java
@@ -28,6 +28,7 @@
 import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.Display_Delegate;
+import android.view.Gravity;
 import android.view.IApplicationToken;
 import android.view.IOnKeyguardExitResult;
 import android.view.IRotationWatcher;
@@ -455,4 +456,9 @@
         return null;
     }
 
+    @Override
+    public int getPreferredOptionsPanelGravity() throws RemoteException {
+        return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+    }
+
 }