diff options
author | Adam Powell <adamp@google.com> | 2011-08-05 20:48:30 -0700 |
---|---|---|
committer | Adam Powell <adamp@google.com> | 2011-08-07 14:17:30 -0700 |
commit | dfee59afb3e4cdcde38f6338f9360655de76da92 (patch) | |
tree | 9581ba1b0c6d7c0034e7ff69fcf85071a9244a1c | |
parent | 5d4967884132647a75d05bcc2ca1f9ce490b372f (diff) | |
download | frameworks_base-dfee59afb3e4cdcde38f6338f9360655de76da92.zip frameworks_base-dfee59afb3e4cdcde38f6338f9360655de76da92.tar.gz frameworks_base-dfee59afb3e4cdcde38f6338f9360655de76da92.tar.bz2 |
Fix bug 5011824 - New Holo overflow menu for physical menu key devices
The new Holo-style overflow menu now appears from the edge of the screen
where the device's physical menu key can be found. The policy determining
this lives in getPreferredOptionsPanelGravity() in WindowManagerService.
Change-Id: I8851a2265547156591e82044e50b5cfc58d3eefa
8 files changed, 260 insertions, 28 deletions
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 @@ interface IWindowManager */ 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 @@ please see themes_device_defaults.xml. <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 @@ please see themes_device_defaults.xml. <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 @@ please see themes_device_defaults.xml. <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 @@ please see themes_device_defaults.xml. <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.graphics.drawable.Drawable; 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.SparseArray; 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.PopupWindow; 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (!st.shownPanelView.hasFocus()) { st.shownPanelView.requestFocus(); } - } else if (!st.isInExpandedMode) { + } else if (!st.isInListMode()) { width = MATCH_PARENT; } @@ -567,7 +582,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { | 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } /** + * 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 (!isCompact) { + 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 (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); } - expandedMenuPresenter.setItemIndexOffset(iconMenuPresenter.getNumActualItemsShown()); - MenuView result = expandedMenuPresenter.getMenuView(decorView); + if (iconMenuPresenter != null) { + listMenuPresenter.setItemIndexOffset( + iconMenuPresenter.getNumActualItemsShown()); + } + MenuView result = listMenuPresenter.getMenuView(decorView); return result; } @@ -3224,6 +3297,69 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } + 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.Slog; 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 @@ public class WindowManagerService extends IWindowManager.Stub } /** + * 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.os.RemoteException; 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 @@ public class BridgeWindowManager implements IWindowManager { return null; } + @Override + public int getPreferredOptionsPanelGravity() throws RemoteException { + return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; + } + } |