diff options
| author | 2020-03-31 17:48:37 -0400 | |
|---|---|---|
| committer | 2020-04-13 17:14:02 -0400 | |
| commit | cf03827a810d8d29b8ec357c82429ba09ef5d3ba (patch) | |
| tree | 9405d515e3b6cdfedc0ac43a9e50ec921c1d80af | |
| parent | 01f473ab78d0edae3eef867d245e5cf82d97fdd5 (diff) | |
Add overflow menu to GlobalActionsDialog.
Test: atest GlobalActionsDialogTest
Test: Power menu items beyond the 3rd will appear in a dropdown menu instead of being truncated when controls are available. If controls are disabled (ex. 'adb shell settings put secure systemui.controls_available 0'), all items should still display in older versions of GlobalActions. Items in power overflow menu are both short- and long-pressable.
Fixes: 152625023
Change-Id: Icdbf8eb7e79a61d490d484f207eeedc47c4882c5
6 files changed, 400 insertions, 81 deletions
diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml index 59c4d011166a..dba003aa82a9 100644 --- a/packages/SystemUI/res/layout/global_actions_grid_v2.xml +++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml @@ -12,34 +12,46 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:theme="@style/qs_theme" - android:gravity="top" android:clipChildren="false" android:clipToPadding="false" android:layout_marginTop="@dimen/global_actions_top_margin" + android:layout_marginLeft="@dimen/global_actions_side_margin" + android:layout_marginRight="@dimen/global_actions_side_margin" > <LinearLayout android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginLeft="@dimen/global_actions_side_margin" - android:layout_marginRight="@dimen/global_actions_side_margin" android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" android:paddingRight="@dimen/global_actions_grid_horizontal_padding" android:paddingTop="@dimen/global_actions_grid_vertical_padding" android:paddingBottom="@dimen/global_actions_grid_vertical_padding" android:orientation="horizontal" - android:gravity="left" + android:gravity="left | center_vertical" android:translationZ="@dimen/global_actions_translate" - /> + > + <RelativeLayout + android:id="@+id/global_actions_overflow_button" + android:layout_width="48dp" + android:layout_height="48dp" + > + <ImageView + android:src="@drawable/ic_more_vert" + android:layout_centerInParent="true" + android:layout_width="24dp" + android:layout_height="24dp" + android:tint="@color/control_more_vert" + /> + </RelativeLayout> + </LinearLayout> </com.android.systemui.globalactions.GlobalActionsFlatLayout> <com.android.systemui.globalactions.MinHeightScrollView - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset" - android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset" - android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset" + android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset" + android:orientation="vertical" > <LinearLayout android:id="@+id/global_actions_grid_root" diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml index da5819c50a7e..2f7fbaff4ed2 100644 --- a/packages/SystemUI/res/values-land/config.xml +++ b/packages/SystemUI/res/values-land/config.xml @@ -34,4 +34,7 @@ <!-- Max number of columns for quick controls area --> <integer name="controls_max_columns">4</integer> + + <!-- Max number of columns for power menu --> + <integer name="power_menu_max_columns">4</integer> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index f549a3253319..62335abd4329 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -536,6 +536,10 @@ <!-- Max number of columns for quick controls area --> <integer name="controls_max_columns">2</integer> + + <!-- Max number of columns for power menu --> + <integer name="power_menu_max_columns">3</integer> + <!-- If the dp width of the available space is <= this value, potentially adjust the number of columns--> <integer name="controls_max_columns_adjust_below_width_dp">320</integer> diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 3f095dc0510e..70294495f2ea 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -75,9 +75,12 @@ import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; +import android.widget.BaseAdapter; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ImageView.ScaleType; +import android.widget.ListPopupWindow; +import android.widget.ListView; import android.widget.TextView; import androidx.annotation.NonNull; @@ -151,19 +154,20 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, /* Valid settings for global actions keys. * see config.xml config_globalActionList */ - private static final String GLOBAL_ACTION_KEY_POWER = "power"; - private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane"; - private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport"; - private static final String GLOBAL_ACTION_KEY_SILENT = "silent"; - private static final String GLOBAL_ACTION_KEY_USERS = "users"; - private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings"; - private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown"; - private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist"; - private static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; - private static final String GLOBAL_ACTION_KEY_RESTART = "restart"; - private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout"; - private static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency"; - private static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot"; + @VisibleForTesting + protected static final String GLOBAL_ACTION_KEY_POWER = "power"; + protected static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane"; + protected static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport"; + protected static final String GLOBAL_ACTION_KEY_SILENT = "silent"; + protected static final String GLOBAL_ACTION_KEY_USERS = "users"; + protected static final String GLOBAL_ACTION_KEY_SETTINGS = "settings"; + protected static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown"; + protected static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist"; + protected static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; + protected static final String GLOBAL_ACTION_KEY_RESTART = "restart"; + protected static final String GLOBAL_ACTION_KEY_LOGOUT = "logout"; + protected static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency"; + protected static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot"; private static final String PREFS_CONTROLS_SEEDING_COMPLETED = "ControlsSeedingCompleted"; private static final String PREFS_CONTROLS_FILE = "controls_prefs"; @@ -191,13 +195,18 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, // Used for RingerModeTracker private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); - private ArrayList<Action> mItems; + @VisibleForTesting + protected ArrayList<Action> mItems; + @VisibleForTesting + protected ArrayList<Action> mOverflowItems; + private ActionsDialog mDialog; private Action mSilentModeAction; private ToggleAction mAirplaneModeOn; private MyAdapter mAdapter; + private MyOverflowAdapter mOverflowAdapter; private boolean mKeyguardShowing = false; private boolean mDeviceProvisioned = false; @@ -459,12 +468,51 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } + @VisibleForTesting + protected boolean shouldShowAction(Action action) { + if (mKeyguardShowing && !action.showDuringKeyguard()) { + return false; + } + if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { + return false; + } + return true; + } + /** - * Create the global actions dialog. - * - * @return A new dialog. + * Returns the maximum number of power menu items to show based on which GlobalActions + * layout is being used. */ - private ActionsDialog createDialog() { + @VisibleForTesting + protected int getMaxShownPowerItems() { + if (shouldShowControls()) { + return mResources.getInteger(com.android.systemui.R.integer.power_menu_max_columns); + } else { + return Integer.MAX_VALUE; + } + } + + /** + * Add a power menu action item for to either the main or overflow items lists, depending on + * whether controls are enabled and whether the max number of shown items has been reached. + */ + private void addActionItem(Action action) { + if (mItems != null && shouldShowAction(action)) { + if (mItems.size() < getMaxShownPowerItems()) { + mItems.add(action); + } else if (mOverflowItems != null) { + mOverflowItems.add(action); + } + } + } + + @VisibleForTesting + protected String[] getDefaultActions() { + return mResources.getStringArray(R.array.config_globalActionsList); + } + + @VisibleForTesting + protected void createActionItems() { // Simple toggle style if there's no vibrator, otherwise use a tri-state if (!mHasVibrator) { mSilentModeAction = new SilentModeToggleAction(); @@ -475,7 +523,13 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, onAirplaneModeChanged(); mItems = new ArrayList<Action>(); - String[] defaultActions = mResources.getStringArray(R.array.config_globalActionsList); + mOverflowItems = new ArrayList<Action>(); + String[] defaultActions = getDefaultActions(); + + // make sure emergency affordance action is first, if needed + if (mEmergencyAffordanceManager.needsEmergencyAffordance()) { + addActionItem(new EmergencyAffordanceAction()); + } ArraySet<String> addedKeys = new ArraySet<String>(); for (int i = 0; i < defaultActions.length; i++) { @@ -485,46 +539,46 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, continue; } if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { - mItems.add(new PowerAction()); + addActionItem(new PowerAction()); } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) { - mItems.add(mAirplaneModeOn); + addActionItem(mAirplaneModeOn); } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) { if (Settings.Global.getInt(mContentResolver, Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) { - mItems.add(new BugReportAction()); + addActionItem(new BugReportAction()); } } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) { if (mShowSilentToggle) { - mItems.add(mSilentModeAction); + addActionItem(mSilentModeAction); } } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) { if (SystemProperties.getBoolean("fw.power_user_switcher", false)) { - addUsersToMenu(mItems); + addUsersToMenu(); } } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) { - mItems.add(getSettingsAction()); + addActionItem(getSettingsAction()); } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) { if (Settings.Secure.getIntForUser(mContentResolver, Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, getCurrentUser().id) != 0 && shouldDisplayLockdown()) { - mItems.add(getLockdownAction()); + addActionItem(getLockdownAction()); } } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) { - mItems.add(getVoiceAssistAction()); + addActionItem(getVoiceAssistAction()); } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) { - mItems.add(getAssistAction()); + addActionItem(getAssistAction()); } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) { - mItems.add(new RestartAction()); + addActionItem(new RestartAction()); } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) { - mItems.add(new ScreenshotAction()); + addActionItem(new ScreenshotAction()); } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) { if (mDevicePolicyManager.isLogoutEnabled() && getCurrentUser().id != UserHandle.USER_SYSTEM) { - mItems.add(new LogoutAction()); + addActionItem(new LogoutAction()); } } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) { if (!mEmergencyAffordanceManager.needsEmergencyAffordance()) { - mItems.add(new EmergencyDialerAction()); + addActionItem(new EmergencyDialerAction()); } } else { Log.e(TAG, "Invalid global action key " + actionKey); @@ -532,17 +586,23 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, // Add here so we don't add more than one. addedKeys.add(actionKey); } + } - if (mEmergencyAffordanceManager.needsEmergencyAffordance()) { - mItems.add(new EmergencyAffordanceAction()); - } + /** + * Create the global actions dialog. + * + * @return A new dialog. + */ + private ActionsDialog createDialog() { + createActionItems(); mAdapter = new MyAdapter(); + mOverflowAdapter = new MyOverflowAdapter(); mDepthController.setShowingHomeControls(shouldShowControls()); - ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, getWalletPanelViewController(), - mDepthController, mSysuiColorExtractor, mStatusBarService, - mNotificationShadeWindowController, + ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter, + getWalletPanelViewController(), mDepthController, mSysuiColorExtractor, + mStatusBarService, mNotificationShadeWindowController, shouldShowControls() ? mControlsUiController : null, mBlurUtils); dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. dialog.setKeyguardShowing(mKeyguardShowing); @@ -1020,7 +1080,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return currentUser == null || currentUser.isPrimary(); } - private void addUsersToMenu(ArrayList<Action> items) { + private void addUsersToMenu() { if (mUserManager.isUserSwitcherEnabled()) { List<UserInfo> users = mUserManager.getUsers(); UserInfo currentUser = getCurrentUser(); @@ -1050,7 +1110,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return false; } }; - items.add(switchToUser); + addActionItem(switchToUser); } } } @@ -1099,11 +1159,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } /** - * The adapter used for the list within the global actions dialog, taking into account whether - * the keyguard is showing via - * {@link com.android.systemui.globalactions.GlobalActionsDialog#mKeyguardShowing} - * and whether the device is provisioned via - * {@link com.android.systemui.globalactions.GlobalActionsDialog#mDeviceProvisioned}. + * The adapter used for power menu items shown in the global actions dialog. */ public class MyAdapter extends MultiListAdapter { private int countItems(boolean separated) { @@ -1111,23 +1167,13 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, for (int i = 0; i < mItems.size(); i++) { final Action action = mItems.get(i); - if (shouldBeShown(action) && action.shouldBeSeparated() == separated) { + if (action.shouldBeSeparated() == separated) { count++; } } return count; } - private boolean shouldBeShown(Action action) { - if (mKeyguardShowing && !action.showDuringKeyguard()) { - return false; - } - if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { - return false; - } - return true; - } - @Override public int countSeparatedItems() { return countItems(true); @@ -1158,7 +1204,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, int filteredPos = 0; for (int i = 0; i < mItems.size(); i++) { final Action action = mItems.get(i); - if (!shouldBeShown(action)) { + if (!shouldShowAction(action)) { continue; } if (filteredPos == position) { @@ -1223,6 +1269,79 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } + /** + * The adapter used for items in the overflow menu. + */ + public class MyOverflowAdapter extends BaseAdapter { + @Override + public int getCount() { + return mOverflowItems != null ? mOverflowItems.size() : 0; + } + + @Override + public Action getItem(int position) { + return mOverflowItems != null ? mOverflowItems.get(position) : null; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + Action action = getItem(position); + if (action == null) { + Log.w(TAG, "No overflow action found at position: " + position); + return null; + } + int viewLayoutResource = com.android.systemui.R.layout.controls_more_item; + View view = convertView != null ? convertView + : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false); + TextView textView = (TextView) view; + textView.setOnClickListener(v -> onClickItem(position)); + if (action.getMessageResId() != 0) { + textView.setText(action.getMessageResId()); + } else { + textView.setText(action.getMessage()); + } + + if (action instanceof LongPressAction) { + textView.setOnLongClickListener(v -> onLongClickItem(position)); + } else { + textView.setOnLongClickListener(null); + } + return textView; + } + + private boolean onLongClickItem(int position) { + final Action action = getItem(position); + if (action instanceof LongPressAction) { + if (mDialog != null) { + mDialog.hidePowerOverflowMenu(); + mDialog.dismiss(); + } else { + Log.w(TAG, "Action long-clicked while mDialog is null."); + } + return ((LongPressAction) action).onLongPress(); + } + return false; + } + + private void onClickItem(int position) { + Action item = getItem(position); + if (!(item instanceof SilentModeTriStateAction)) { + if (mDialog != null) { + mDialog.hidePowerOverflowMenu(); + mDialog.dismiss(); + } else { + Log.w(TAG, "Action clicked while mDialog is null."); + } + item.onPress(); + } + } + } + // note: the scheme below made more sense when we were planning on having // 8 different things in the global actions dialog. seems overkill with // only 3 items now, but may as well keep this flexible approach so it will @@ -1248,8 +1367,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, boolean showDuringKeyguard(); /** - * @return whether this action should appear in the dialog before the device is - * provisioned.onlongpress + * @return whether this action should appear in the dialog before the + * device is provisioned.f */ boolean showBeforeProvisioning(); @@ -1258,6 +1377,18 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, default boolean shouldBeSeparated() { return false; } + + /** + * Return the id of the message associated with this action, or 0 if it doesn't have one. + * @return + */ + int getMessageResId(); + + /** + * Return the message associated with this action, or null if it doesn't have one. + * @return + */ + CharSequence getMessage(); } /** @@ -1309,6 +1440,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } + + public int getMessageResId() { + return mMessageResId; + } + + public CharSequence getMessage() { + return mMessage; + } + public View create( Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { View v = inflater.inflate(getActionLayoutId(), parent, false /* attach */); @@ -1396,6 +1536,23 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return context.getString(mMessageResId); } + private boolean isOn() { + return mState == ToggleState.On || mState == ToggleState.TurningOn; + } + + @Override + public CharSequence getMessage() { + return null; + } + @Override + public int getMessageResId() { + return isOn() ? mEnabledStatusMessageResId : mDisabledStatusMessageResId; + } + + private int getIconResId() { + return isOn() ? mEnabledIconResId : mDisabledIconResid; + } + public View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { willCreate(); @@ -1405,17 +1562,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, ImageView icon = (ImageView) v.findViewById(R.id.icon); TextView messageView = (TextView) v.findViewById(R.id.message); final boolean enabled = isEnabled(); - boolean on = ((mState == ToggleState.On) || (mState == ToggleState.TurningOn)); if (messageView != null) { - messageView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); + messageView.setText(getMessageResId()); messageView.setEnabled(enabled); messageView.setSelected(true); // necessary for marquee to work } if (icon != null) { - icon.setImageDrawable(context.getDrawable( - (on ? mEnabledIconResId : mDisabledIconResid))); + icon.setImageDrawable(context.getDrawable(getIconResId())); icon.setEnabled(enabled); } @@ -1553,6 +1708,16 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return null; } + @Override + public int getMessageResId() { + return 0; + } + + @Override + public CharSequence getMessage() { + return null; + } + public View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false); @@ -1708,6 +1873,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final Context mContext; private final MyAdapter mAdapter; + private final MyOverflowAdapter mOverflowAdapter; private final IStatusBarService mStatusBarService; private final IBinder mToken = new Binder(); private MultiListLayout mGlobalActionsLayout; @@ -1722,11 +1888,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final NotificationShadeWindowController mNotificationShadeWindowController; private final NotificationShadeDepthController mDepthController; private final BlurUtils mBlurUtils; + private ListPopupWindow mOverflowPopup; private ControlsUiController mControlsUiController; private ViewGroup mControlsView; - ActionsDialog(Context context, MyAdapter adapter, + ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter, GlobalActionsPanelPlugin.PanelViewController plugin, NotificationShadeDepthController depthController, SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, @@ -1735,6 +1902,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions); mContext = context; mAdapter = adapter; + mOverflowAdapter = overflowAdapter; mDepthController = depthController; mColorExtractor = sysuiColorExtractor; mStatusBarService = statusBarService; @@ -1817,6 +1985,45 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } + private ListPopupWindow createPowerOverflowPopup() { + ListPopupWindow popup = new ListPopupWindow(new ContextThemeWrapper( + mContext, com.android.systemui.R.style.Control_ListPopupWindow)); + popup.setWindowLayoutType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); + View overflowButton = + findViewById(com.android.systemui.R.id.global_actions_overflow_button); + popup.setAnchorView(overflowButton); + int parentWidth = mGlobalActionsLayout.getWidth(); + // arbitrarily set the menu width to half of parent + // TODO: Logic for menu sizing based on contents. + int halfParentWidth = Math.round(parentWidth * 0.5f); + popup.setContentWidth(halfParentWidth); + popup.setAdapter(mOverflowAdapter); + popup.setModal(true); + return popup; + } + + private void showPowerOverflowMenu() { + mOverflowPopup.show(); + + // Width is fixed to slightly more than half of the GlobalActionsLayout container. + // TODO: Resize the width of this dialog based on the sizes of the items in it. + int width = Math.round(mGlobalActionsLayout.getWidth() * 0.6f); + + ListView listView = mOverflowPopup.getListView(); + listView.setDividerHeight(mContext.getResources() + .getDimensionPixelSize(com.android.systemui.R.dimen.control_list_divider)); + listView.setDivider(mContext.getResources().getDrawable( + com.android.systemui.R.drawable.controls_list_divider)); + mOverflowPopup.setWidth(width); + mOverflowPopup.setHorizontalOffset(-width + mOverflowPopup.getAnchorView().getWidth()); + mOverflowPopup.setVerticalOffset(mOverflowPopup.getAnchorView().getHeight()); + mOverflowPopup.show(); + } + + private void hidePowerOverflowMenu() { + mOverflowPopup.dismiss(); + } + private void initializeLayout() { setContentView(getGlobalActionsLayoutId(mContext)); fixNavBarClipping(); @@ -1835,6 +2042,18 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mGlobalActionsLayout.setRotationListener(this::onRotate); mGlobalActionsLayout.setAdapter(mAdapter); + mOverflowPopup = createPowerOverflowPopup(); + + View overflowButton = findViewById( + com.android.systemui.R.id.global_actions_overflow_button); + if (overflowButton != null) { + if (mOverflowAdapter.getCount() > 0) { + overflowButton.setOnClickListener((view) -> showPowerOverflowMenu()); + } else { + overflowButton.setVisibility(View.GONE); + } + } + View globalActionsParent = (View) mGlobalActionsLayout.getParent(); globalActionsParent.setOnClickListener(v -> dismiss()); @@ -2090,9 +2309,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return isPanelDebugModeEnabled(context); } - private boolean shouldShowControls() { + @VisibleForTesting + protected boolean shouldShowControls() { return mKeyguardStateController.isUnlocked() && mControlsUiController.getAvailable() && !mControlsServiceInfos.isEmpty(); } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java index f1025615783b..2f32d972449e 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java @@ -32,7 +32,6 @@ import com.android.systemui.R; * Flat, single-row implementation of the button layout created by the global actions dialog. */ public class GlobalActionsFlatLayout extends GlobalActionsLayout { - private static final int MAX_ITEMS = 4; public GlobalActionsFlatLayout(Context context, AttributeSet attrs) { super(context, attrs); } @@ -54,11 +53,28 @@ public class GlobalActionsFlatLayout extends GlobalActionsLayout { return null; } + private View getOverflowButton() { + return findViewById(com.android.systemui.R.id.global_actions_overflow_button); + } + @Override protected void addToListView(View v, boolean reverse) { - // only add items to the list view if we haven't hit our max yet - if (getListView().getChildCount() < MAX_ITEMS) { - super.addToListView(v, reverse); + super.addToListView(v, reverse); + View overflowButton = getOverflowButton(); + // if there's an overflow button, make sure it stays at the end + if (overflowButton != null) { + getListView().removeView(overflowButton); + super.addToListView(overflowButton, reverse); + } + } + + @Override + protected void removeAllListViews() { + View overflowButton = getOverflowButton(); + super.removeAllListViews(); + // if there's an overflow button, add it back after clearing the list views + if (overflowButton != null) { + super.addToListView(overflowButton, false); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java index 3af164db4b80..b44b23a9f51e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -16,6 +16,11 @@ package com.android.systemui.globalactions; +import static junit.framework.Assert.assertEquals; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -200,4 +205,63 @@ public class GlobalActionsDialogTest extends SysuiTestCase { verify(mUiEventLogger, times(1)) .log(event); } + + @Test + public void testCreateActionItems_maxThree() { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + // allow 3 items to be shown + doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); + // ensure items are not blocked by keyguard or device provisioning + doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT, + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + mGlobalActionsDialog.createActionItems(); + + assertEquals(3, mGlobalActionsDialog.mItems.size()); + assertEquals(1, mGlobalActionsDialog.mOverflowItems.size()); + } + + @Test + public void testCreateActionItems_maxAny() { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + // allow any number of power menu items to be shown + doReturn(Integer.MAX_VALUE).when(mGlobalActionsDialog).getMaxShownPowerItems(); + // ensure items are not blocked by keyguard or device provisioning + doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT, + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + mGlobalActionsDialog.createActionItems(); + + assertEquals(4, mGlobalActionsDialog.mItems.size()); + assertEquals(0, mGlobalActionsDialog.mOverflowItems.size()); + } + + @Test + public void testCreateActionItems_maxThree_itemNotShown() { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + // allow only 3 items to be shown + doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + // screenshot blocked because device not provisioned + GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + mGlobalActionsDialog.createActionItems(); + + assertEquals(3, mGlobalActionsDialog.mItems.size()); + assertEquals(0, mGlobalActionsDialog.mOverflowItems.size()); + } } |