Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 57d60d2c authored by Aaron Heuckroth's avatar Aaron Heuckroth
Browse files

Refactor MultiListLayout to improve encapsulation.

Test: Automated tests pass, power menus render as expected with 3-4 items.
Change-Id: Iae2fbd9c46b4a2955665ae25838f75507461bba7
parent 45614ed4
Loading
Loading
Loading
Loading
+34 −15
Original line number Diff line number Diff line
@@ -32,10 +32,13 @@ import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.widget.LinearLayout;

import com.android.systemui.globalactions.GlobalActionsDialog;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.leak.RotationUtils;

import java.util.ArrayList;

/**
 * Layout for placing two containers at a specific physical position on the device, relative to the
 * device's hardware, regardless of screen rotation.
@@ -89,15 +92,6 @@ public class HardwareUiLayout extends MultiListLayout implements Tunable {
        }
    }

    @Override
    public ViewGroup getParentView(boolean separated, int index, int rotation) {
        if (separated) {
            return getSeparatedView();
        } else {
            return getListView();
        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
@@ -221,7 +215,7 @@ public class HardwareUiLayout extends MultiListLayout implements Tunable {
        } else {
            rotateLeft();
        }
        if (mHasSeparatedView) {
        if (mSeparated) {
            if (from == ROTATION_SEASCAPE || to == ROTATION_SEASCAPE) {
                // Separated view has top margin, so seascape separated view need special rotation,
                // not a full left or right rotation.
@@ -261,6 +255,31 @@ public class HardwareUiLayout extends MultiListLayout implements Tunable {
        }
    }

    @Override
    public void onUpdateList() {
        removeAllItems();
        ArrayList<GlobalActionsDialog.Action> separatedActions =
                mAdapter.getSeparatedItems(mSeparated);
        ArrayList<GlobalActionsDialog.Action> listActions = mAdapter.getListItems(mSeparated);

        for (int i = 0; i < mAdapter.getCount(); i++) {
            Object action = mAdapter.getItem(i);
            int separatedIndex = separatedActions.indexOf(action);
            ViewGroup parent;
            if (separatedIndex != -1) {
                parent = getSeparatedView();
            } else {
                int listIndex = listActions.indexOf(action);
                parent = getListView();
            }
            View v = mAdapter.getView(i, null, parent);
            final int pos = i;
            v.setOnClickListener(view -> mAdapter.onClickItem(pos));
            v.setOnLongClickListener(view -> mAdapter.onLongClickItem(pos));
            parent.addView(v);
        }
    }

    private void rotateRight() {
        rotateRight(this);
        rotateRight(mList);
@@ -442,8 +461,8 @@ public class HardwareUiLayout extends MultiListLayout implements Tunable {
        if (mList == null) return;
        // If got separated button, setRotatedBackground to false,
        // all items won't get white background.
        mListBackground.setRotatedBackground(mHasSeparatedView);
        mSeparatedViewBackground.setRotatedBackground(mHasSeparatedView);
        mListBackground.setRotatedBackground(mSeparated);
        mSeparatedViewBackground.setRotatedBackground(mSeparated);
        if (mDivision != null && mDivision.getVisibility() == VISIBLE) {
            int index = mRotatedBackground ? 0 : 1;
            mDivision.getLocationOnScreen(mTmp2);
@@ -494,21 +513,21 @@ public class HardwareUiLayout extends MultiListLayout implements Tunable {
            case RotationUtils.ROTATION_LANDSCAPE:
                defaultTopPadding = getPaddingLeft();
                viewsTotalHeight = mList.getMeasuredWidth() + mSeparatedView.getMeasuredWidth();
                separatedViewTopMargin = mHasSeparatedView ? params.leftMargin : 0;
                separatedViewTopMargin = mSeparated ? params.leftMargin : 0;
                screenHeight = getMeasuredWidth();
                targetGravity = Gravity.CENTER_HORIZONTAL|Gravity.TOP;
                break;
            case RotationUtils.ROTATION_SEASCAPE:
                defaultTopPadding = getPaddingRight();
                viewsTotalHeight = mList.getMeasuredWidth() + mSeparatedView.getMeasuredWidth();
                separatedViewTopMargin = mHasSeparatedView ? params.leftMargin : 0;
                separatedViewTopMargin = mSeparated ? params.leftMargin : 0;
                screenHeight = getMeasuredWidth();
                targetGravity = Gravity.CENTER_HORIZONTAL|Gravity.BOTTOM;
                break;
            default: // Portrait
                defaultTopPadding = getPaddingTop();
                viewsTotalHeight = mList.getMeasuredHeight() + mSeparatedView.getMeasuredHeight();
                separatedViewTopMargin = mHasSeparatedView ? params.topMargin : 0;
                separatedViewTopMargin = mSeparated ? params.topMargin : 0;
                screenHeight = getMeasuredHeight();
                targetGravity = Gravity.CENTER_VERTICAL|Gravity.RIGHT;
                break;
+57 −37
Original line number Diff line number Diff line
@@ -21,19 +21,20 @@ import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;

import com.android.systemui.util.leak.RotationUtils;

import java.util.ArrayList;

/**
 * Layout class representing the Global Actions menu which appears when the power button is held.
 */
public abstract class MultiListLayout extends LinearLayout {
    protected boolean mHasOutsideTouch;
    protected boolean mHasSeparatedView;

    protected int mExpectedSeparatedItemCount;
    protected int mExpectedListItemCount;
    protected boolean mSeparated;
    protected MultiListAdapter mAdapter;

    protected int mRotation;
    protected RotationListener mRotationListener;
@@ -52,18 +53,6 @@ public abstract class MultiListLayout extends LinearLayout {
     */
    public abstract void removeAllItems();

    /**
     * Get the parent view which will be used to contain the item at the specified index.
     * @param separated Whether or not this index refers to a position in the separated or list
     *                  container.
     * @param index The index of the item within the container.
     * @param rotation Specifies the rotation of the device, which is used in some cases to
     *                 determine child ordering.
     * @return The parent ViewGroup which will be used to contain the specified item
     * after it has been added to the layout.
     */
    public abstract ViewGroup getParentView(boolean separated, int index, int rotation);

    /**
     * Sets the divided view, which may have a differently-colored background.
     */
@@ -81,32 +70,19 @@ public abstract class MultiListLayout extends LinearLayout {
    }

    /**
     * Sets the number of items expected to be rendered in the separated container. This allows the
     * layout to correctly determine which parent containers will be used for items before they have
     * beenadded to the layout.
     * @param count The number of items expected.
     */
    public void setExpectedSeparatedItemCount(int count) {
        mExpectedSeparatedItemCount = count;
    }

    /**
     * Sets the number of items expected to be rendered in the list container. This allows the
     * layout to correctly determine which parent containers will be used for items before they have
     * beenadded to the layout.
     * @param count The number of items expected.
     * Sets whether the separated view should be shown, and handles updating visibility on
     * that view.
     */
    public void setExpectedListItemCount(int count) {
        mExpectedListItemCount = count;
    public void setSeparated(boolean separated) {
        mSeparated = separated;
        setSeparatedViewVisibility(separated);
    }

    /**
     * Sets whether the separated view should be shown, and handles updating visibility on
     * that view.
     * Sets the adapter used to inflate items.
     */
    public void setHasSeparatedView(boolean hasSeparatedView) {
        mHasSeparatedView = hasSeparatedView;
        setSeparatedViewVisibility(hasSeparatedView);
    public void setAdapter(MultiListAdapter adapter) {
        mAdapter = adapter;
    }

    /**
@@ -136,6 +112,19 @@ public abstract class MultiListLayout extends LinearLayout {
        }
    }

    /**
     * Update the list of items in both the separated and list views.
     * For this to work, mAdapter must already have been set.
     */
    public void updateList() {
        if (mAdapter == null) {
            throw new IllegalStateException("mAdapter must be set before calling updateList");
        }
        onUpdateList();
    }

    protected abstract void onUpdateList();

    public void setRotationListener(RotationListener listener) {
        mRotationListener = listener;
    }
@@ -157,4 +146,35 @@ public abstract class MultiListLayout extends LinearLayout {
    public interface RotationListener {
        void onRotate(int from, int to);
    }

    /**
     * Adapter class for converting items into child views for MultiListLayout and handling
     * callbacks for input events.
     */
    public abstract static class MultiListAdapter extends BaseAdapter {
        /**
         * Creates an ArrayList of items which should be rendered in the separated view.
         * @param useSeparatedView is true if the separated view will be used, false otherwise.
         */
        public abstract ArrayList getSeparatedItems(boolean useSeparatedView);

        /**
         * Creates an ArrayList of items which should be rendered in the list view.
         * @param useSeparatedView True if the separated view will be used, false otherwise.
         */
        public abstract ArrayList getListItems(boolean useSeparatedView);

        /**
         * Callback to run when an individual item is clicked or pressed.
         * @param position The index of the item which was clicked.
         */
        public abstract void onClickItem(int position);

        /**
         * Callback to run when an individual item is long-clicked or long-pressed.
         * @param position The index of the item which was long-clicked.
         * @return True if the long-click was handled, false otherwise.
         */
        public abstract boolean onLongClickItem(int position);
    }
}
+46 −92
Original line number Diff line number Diff line
@@ -65,8 +65,6 @@ import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
@@ -87,6 +85,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.MultiListLayout;
import com.android.systemui.MultiListLayout.MultiListAdapter;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
@@ -99,16 +98,14 @@ import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolat

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * Helper to show the global actions dialog.  Each item is an {@link Action} that
 * may show depending on whether the keyguard is showing, and whether the device
 * is provisioned.
 */
class GlobalActionsDialog implements DialogInterface.OnDismissListener,
        DialogInterface.OnClickListener, DialogInterface.OnShowListener,
        ConfigurationController.ConfigurationListener {
public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
        DialogInterface.OnShowListener, ConfigurationController.ConfigurationListener {

    static public final String SYSTEM_DIALOG_REASON_KEY = "reason";
    static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
@@ -158,7 +155,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
    private boolean mHasVibrator;
    private boolean mHasLogoutButton;
    private boolean mHasLockdownButton;
    private boolean mSeparatedEmergencyButtonEnabled;
    private boolean mUseSeparatedList;
    private final boolean mShowSilentToggle;
    private final EmergencyAffordanceManager mEmergencyAffordanceManager;
    private final ScreenshotHelper mScreenshotHelper;
@@ -336,7 +333,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
        ArraySet<String> addedKeys = new ArraySet<String>();
        mHasLogoutButton = false;
        mHasLockdownButton = false;
        mSeparatedEmergencyButtonEnabled = true;
        mUseSeparatedList = true;
        for (int i = 0; i < defaultActions.length; i++) {
            String actionKey = defaultActions[i];
            if (addedKeys.contains(actionKey)) {
@@ -384,7 +381,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
                    mHasLogoutButton = true;
                }
            } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) {
                if (mSeparatedEmergencyButtonEnabled
                if (mUseSeparatedList
                        && !mEmergencyAffordanceManager.needsEmergencyAffordance()) {
                    mItems.add(new EmergencyDialerAction());
                }
@@ -401,14 +398,6 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,

        mAdapter = new MyAdapter();

        OnItemLongClickListener onItemLongClickListener = (parent, view, position, id) -> {
            final Action action = mAdapter.getItem(position);
            if (action instanceof LongPressAction) {
                mDialog.dismiss();
                return ((LongPressAction) action).onLongPress();
            }
            return false;
        };
        GlobalActionsPanelPlugin.PanelViewController panelViewController =
                mPanelExtension.get() != null
                        ? mPanelExtension.get().onPanelShown(() -> {
@@ -417,8 +406,8 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
                            }
                        })
                        : null;
        ActionsDialog dialog = new ActionsDialog(mContext, this, mAdapter, onItemLongClickListener,
                mSeparatedEmergencyButtonEnabled, panelViewController);
        ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mUseSeparatedList,
                panelViewController);
        dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
        dialog.setKeyguardShowing(mKeyguardShowing);

@@ -548,7 +537,6 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
        }
    }


    private class ScreenshotAction extends SinglePressAction implements LongPressAction {
        public ScreenshotAction() {
            super(R.drawable.ic_screenshot, R.string.global_action_screenshot);
@@ -713,7 +701,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,

    private Action getEmergencyAction() {
        Drawable emergencyIcon = mContext.getDrawable(R.drawable.emergency_icon);
        if(!mSeparatedEmergencyButtonEnabled) {
        if (!mUseSeparatedList) {
            // use un-colored legacy treatment
            emergencyIcon.setTintList(null);
        }
@@ -906,15 +894,6 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
        }
    }

    /** {@inheritDoc} */
    public void onClick(DialogInterface dialog, int which) {
        Action item = mAdapter.getItem(which);
        if (!(item instanceof SilentModeTriStateAction)) {
            dialog.dismiss();
        }
        item.onPress();
    }

    /** {@inheritDoc} */
    public void onShow(DialogInterface dialog) {
        MetricsLogger.visible(mContext, MetricsEvent.POWER_MENU);
@@ -927,11 +906,10 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
     * the device is provisioned
     * via {@link com.android.systemui.globalactions.GlobalActionsDialog#mDeviceProvisioned}.
     */
    private class MyAdapter extends BaseAdapter {

    public class MyAdapter extends MultiListAdapter {
        @Override
        public int getCount() {
            int count = 0;

            for (int i = 0; i < mItems.size(); i++) {
                final Action action = mItems.get(i);

@@ -951,7 +929,8 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
            return getItem(position).isEnabled();
        }

        public ArrayList<Action> getSeparatedActions(boolean shouldUseSeparatedView) {
        @Override
        public ArrayList<Action> getSeparatedItems(boolean shouldUseSeparatedView) {
            ArrayList<Action> separatedActions = new ArrayList<Action>();
            if (!shouldUseSeparatedView) {
                return separatedActions;
@@ -965,7 +944,8 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
            return separatedActions;
        }

        public ArrayList<Action> getListActions(boolean shouldUseSeparatedView) {
        @Override
        public ArrayList<Action> getListItems(boolean shouldUseSeparatedView) {
            if (!shouldUseSeparatedView) {
                return new ArrayList<Action>(mItems);
            }
@@ -984,6 +964,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
            return false;
        }

        @Override
        public Action getItem(int position) {

            int filteredPos = 0;
@@ -1013,6 +994,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            Action action = getItem(position);
            View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
@@ -1022,6 +1004,25 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
            }
            return view;
        }

        @Override
        public boolean onLongClickItem(int position) {
            final Action action = mAdapter.getItem(position);
            if (action instanceof LongPressAction) {
                mDialog.dismiss();
                return ((LongPressAction) action).onLongPress();
            }
            return false;
        }

        @Override
        public void onClickItem(int position) {
            Action item = mAdapter.getItem(position);
            if (!(item instanceof SilentModeTriStateAction)) {
                mDialog.dismiss();
            }
            item.onPress();
        }
    }

    // note: the scheme below made more sense when we were planning on having
@@ -1033,7 +1034,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
    /**
     * What each item in the global actions dialog must be able to support.
     */
    private interface Action {
    public interface Action {
        /**
         * @return Text that will be announced when dialog is created.  null
         * for none.
@@ -1052,7 +1053,8 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,

        /**
         * @return whether this action should appear in the dialog before the
         * device is provisioned.
         * device is provisioned.onlongpress
         *
         */
        boolean showBeforeProvisioning();

@@ -1488,26 +1490,21 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
        private final Context mContext;
        private final MyAdapter mAdapter;
        private MultiListLayout mGlobalActionsLayout;
        private final OnClickListener mClickListener;
        private final OnItemLongClickListener mLongClickListener;
        private final Drawable mBackgroundDrawable;
        private final ColorExtractor mColorExtractor;
        private final GlobalActionsPanelPlugin.PanelViewController mPanelController;
        private boolean mKeyguardShowing;
        private boolean mShouldDisplaySeparatedButton;
        private boolean mUseSeparatedList;
        private boolean mShowing;
        private final float mScrimAlpha;

        public ActionsDialog(Context context, OnClickListener clickListener, MyAdapter adapter,
                OnItemLongClickListener longClickListener, boolean shouldDisplaySeparatedButton,
        ActionsDialog(Context context, MyAdapter adapter, boolean separated,
                GlobalActionsPanelPlugin.PanelViewController plugin) {
            super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
            mContext = context;
            mAdapter = adapter;
            mClickListener = clickListener;
            mLongClickListener = longClickListener;
            mColorExtractor = Dependency.get(SysuiColorExtractor.class);
            mShouldDisplaySeparatedButton = shouldDisplaySeparatedButton;
            mUseSeparatedList = separated;

            // Window initialization
            Window window = getWindow();
@@ -1573,7 +1570,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
            mGlobalActionsLayout = (MultiListLayout)
                    findViewById(com.android.systemui.R.id.global_actions_view);
            mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss());
            mGlobalActionsLayout.setHasSeparatedView(mShouldDisplaySeparatedButton);
            mGlobalActionsLayout.setSeparated(mUseSeparatedList);
            mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() {
                @Override
                public boolean dispatchPopulateAccessibilityEvent(
@@ -1584,6 +1581,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
                }
            });
            mGlobalActionsLayout.setRotationListener(this::onRotate);
            mGlobalActionsLayout.setAdapter(mAdapter);
        }

        private boolean isPanelEnabled(Context context) {
@@ -1601,55 +1599,11 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
            return com.android.systemui.R.layout.global_actions_wrapped;
        }

        private void updateList() {
            mGlobalActionsLayout.removeAllItems();
            ArrayList<Action> separatedActions =
                    mAdapter.getSeparatedActions(mShouldDisplaySeparatedButton);
            ArrayList<Action> listActions = mAdapter.getListActions(mShouldDisplaySeparatedButton);
            mGlobalActionsLayout.setExpectedListItemCount(listActions.size());
            mGlobalActionsLayout.setExpectedSeparatedItemCount(separatedActions.size());
            int rotation = RotationUtils.getRotation(mContext);

            boolean reverse = false; // should we add items to parents in the reverse order?
            if (isGridEnabled(mContext)) {
                if (rotation == RotationUtils.ROTATION_NONE
                        || rotation == RotationUtils.ROTATION_SEASCAPE) {
                    reverse = !reverse; // if we're in portrait or seascape, reverse items
                }
                if (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
                        == View.LAYOUT_DIRECTION_RTL) {
                    reverse = !reverse; // if we're in an RTL language, reverse items (again)
                }
            }

            for (int i = 0; i < mAdapter.getCount(); i++) {
                Action action = mAdapter.getItem(i);
                int separatedIndex = separatedActions.indexOf(action);
                ViewGroup parent;
                if (separatedIndex != -1) {
                    parent = mGlobalActionsLayout.getParentView(true, separatedIndex, rotation);
                } else {
                    int listIndex = listActions.indexOf(action);
                    parent = mGlobalActionsLayout.getParentView(false, listIndex, rotation);
                }
                View v = mAdapter.getView(i, null, parent);
                final int pos = i;
                v.setOnClickListener(view -> mClickListener.onClick(this, pos));
                v.setOnLongClickListener(view ->
                        mLongClickListener.onItemLongClick(null, v, pos, 0));
                if (reverse) {
                    parent.addView(v, 0); // reverse order of items
                } else {
                    parent.addView(v);
                }
            }
        }

        @Override
        protected void onStart() {
            super.setCanceledOnTouchOutside(true);
            super.onStart();
            updateList();
            mGlobalActionsLayout.updateList();

            if (mBackgroundDrawable instanceof GradientDrawable) {
                Point displaySize = new Point();
@@ -1767,7 +1721,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
        public void onRotate(int from, int to) {
            if (mShowing && isGridEnabled(mContext)) {
                initializeLayout();
                updateList();
                mGlobalActionsLayout.updateList();
            }
        }
    }
+51 −3

File changed.

Preview size limit exceeded, changes collapsed.

+1 −3
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.systemui.globalactions;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
@@ -39,6 +38,7 @@ import android.widget.LinearLayout;
 */

public class ListGridLayout extends LinearLayout {
    private static final String TAG = "ListGridLayout";
    private int mExpectedCount;
    private int mRows;
    private int mColumns;
@@ -103,12 +103,10 @@ public class ListGridLayout extends LinearLayout {
                setSublistVisibility(i, false);
            }
        }

    }

    private void setSublistVisibility(int index, boolean visible) {
        View subList = getChildAt(index);
        Log.d("ListGrid", "index: " + index  + ", visibility: "  + visible);
        if (subList != null) {
            subList.setVisibility(visible ? View.VISIBLE : View.GONE);
        }