summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Aran Ink <nesciosquid@google.com> 2020-03-31 17:48:37 -0400
committer Aran Ink <nesciosquid@google.com> 2020-04-13 17:14:02 -0400
commitcf03827a810d8d29b8ec357c82429ba09ef5d3ba (patch)
tree9405d515e3b6cdfedc0ac43a9e50ec921c1d80af
parent01f473ab78d0edae3eef867d245e5cf82d97fdd5 (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
-rw-r--r--packages/SystemUI/res/layout/global_actions_grid_v2.xml34
-rw-r--r--packages/SystemUI/res/values-land/config.xml3
-rw-r--r--packages/SystemUI/res/values/config.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java352
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java64
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());
+ }
}