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

Commit 7b40594b authored by Tony Wickham's avatar Tony Wickham Committed by Android (Google) Code Review
Browse files

Merge changes I36f6cfb8,I7c784765

* changes:
  Create new window for Taskbar Nav Buttons when taskbar is focusable
  Make taskbar focusable when folder is open to get IME input
parents 9df1ba08 66971f87
Loading
Loading
Loading
Loading
+83 −1
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;

import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
@@ -55,9 +56,11 @@ import android.util.Property;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.View.OnClickListener;
import android.view.View.OnHoverListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -68,10 +71,13 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AlphaUpdateListener;
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.AnimatedFloat;
import com.android.systemui.shared.rotation.FloatingRotationButton;
import com.android.systemui.shared.rotation.RotationButton;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.shared.system.ViewTreeObserverWrapper;

import java.util.ArrayList;
import java.util.function.IntPredicate;
@@ -98,6 +104,8 @@ public class NavbarButtonsViewController {

    private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE;

    private static final String NAV_BUTTONS_SEPARATE_WINDOW_TITLE = "Taskbar Nav Buttons";

    private final ArrayList<StatePropertyHolder> mPropertyHolders = new ArrayList<>();
    private final ArrayList<ImageView> mAllButtons = new ArrayList<>();
    private int mState;
@@ -134,6 +142,12 @@ public class NavbarButtonsViewController {
    private View mHomeButton;
    private FloatingRotationButton mFloatingRotationButton;

    // Variables for moving nav buttons to a separate window above IME
    private boolean mAreNavButtonsInSeparateWindow = false;
    private BaseDragLayer<TaskbarActivityContext> mSeparateWindowParent; // Initialized in init.
    private final ViewTreeObserverWrapper.OnComputeInsetsListener mSeparateWindowInsetsComputer =
            this::onComputeInsetsForSeparateWindow;

    public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
        mContext = context;
        mNavButtonsView = navButtonsView;
@@ -321,6 +335,21 @@ public class NavbarButtonsViewController {
                    R.id.notifications_button);
        }

        // Initialize things needed to move nav buttons to separate window.
        mSeparateWindowParent = new BaseDragLayer<TaskbarActivityContext>(mContext, null, 0) {
            @Override
            public void recreateControllers() {
                mControllers = new TouchController[0];
            }

            @Override
            protected boolean canFindActiveController() {
                // We don't have any controllers, but we don't want any floating views such as
                // folder to intercept, either. This ensures nav buttons can always be pressed.
                return false;
            }
        };
        mSeparateWindowParent.recreateControllers();
    }

    private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
@@ -456,7 +485,7 @@ public class NavbarButtonsViewController {
    /**
     * Adds the bounds corresponding to all visible buttons to provided region
     */
    public void addVisibleButtonsRegion(TaskbarDragLayer parent, Region outRegion) {
    public void addVisibleButtonsRegion(BaseDragLayer<?> parent, Region outRegion) {
        int count = mAllButtons.size();
        for (int i = 0; i < count; i++) {
            View button = mAllButtons.get(i);
@@ -561,6 +590,59 @@ public class NavbarButtonsViewController {
        if (mFloatingRotationButton != null) {
            mFloatingRotationButton.hide();
        }

        moveNavButtonsBackToTaskbarWindow();
    }

    /**
     * Moves mNavButtonsView from TaskbarDragLayer to a placeholder BaseDragLayer on a new window.
     */
    public void moveNavButtonsToNewWindow() {
        if (mAreNavButtonsInSeparateWindow) {
            return;
        }

        mSeparateWindowParent.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
            @Override
            public void onViewAttachedToWindow(View view) {
                ViewTreeObserverWrapper.addOnComputeInsetsListener(
                        mSeparateWindowParent.getViewTreeObserver(), mSeparateWindowInsetsComputer);
            }

            @Override
            public void onViewDetachedFromWindow(View view) {
                mSeparateWindowParent.removeOnAttachStateChangeListener(this);
                ViewTreeObserverWrapper.removeOnComputeInsetsListener(
                        mSeparateWindowInsetsComputer);
            }
        });

        mAreNavButtonsInSeparateWindow = true;
        mContext.getDragLayer().removeView(mNavButtonsView);
        mSeparateWindowParent.addView(mNavButtonsView);
        WindowManager.LayoutParams windowLayoutParams = mContext.createDefaultWindowLayoutParams();
        windowLayoutParams.setTitle(NAV_BUTTONS_SEPARATE_WINDOW_TITLE);
        mContext.addWindowView(mSeparateWindowParent, windowLayoutParams);

    }

    /**
     * Moves mNavButtonsView from its temporary window and reattaches it to TaskbarDragLayer.
     */
    public void moveNavButtonsBackToTaskbarWindow() {
        if (!mAreNavButtonsInSeparateWindow) {
            return;
        }

        mAreNavButtonsInSeparateWindow = false;
        mContext.removeWindowView(mSeparateWindowParent);
        mSeparateWindowParent.removeView(mNavButtonsView);
        mContext.getDragLayer().addView(mNavButtonsView);
    }

    private void onComputeInsetsForSeparateWindow(ViewTreeObserverWrapper.InsetsInfo insetsInfo) {
        addVisibleButtonsRegion(mSeparateWindowParent, insetsInfo.touchableRegion);
        insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
    }

    private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback {
+59 −23
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar;

import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;

@@ -189,21 +190,7 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ

    public void init(TaskbarSharedState sharedState) {
        mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
        mWindowLayoutParams = new WindowManager.LayoutParams(
                MATCH_PARENT,
                mLastRequestedNonFullscreenHeight,
                TYPE_NAVIGATION_BAR_PANEL,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
                PixelFormat.TRANSLUCENT);
        mWindowLayoutParams.setTitle(WINDOW_TITLE);
        mWindowLayoutParams.packageName = getPackageName();
        mWindowLayoutParams.gravity = Gravity.BOTTOM;
        mWindowLayoutParams.setFitInsetsTypes(0);
        mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
        mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
        mWindowLayoutParams.privateFlags =
                WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
        mWindowLayoutParams = createDefaultWindowLayoutParams();

        WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance();
        wmWrapper.setProvidesInsetsTypes(
@@ -224,6 +211,27 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
        mWindowManager.addView(mDragLayer, mWindowLayoutParams);
    }

    /** Creates LayoutParams for adding a view directly to WindowManager as a new window */
    public WindowManager.LayoutParams createDefaultWindowLayoutParams() {
        WindowManager.LayoutParams windowLayoutParams = new WindowManager.LayoutParams(
                MATCH_PARENT,
                mLastRequestedNonFullscreenHeight,
                TYPE_NAVIGATION_BAR_PANEL,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
                PixelFormat.TRANSLUCENT);
        windowLayoutParams.setTitle(WINDOW_TITLE);
        windowLayoutParams.packageName = getPackageName();
        windowLayoutParams.gravity = Gravity.BOTTOM;
        windowLayoutParams.setFitInsetsTypes(0);
        windowLayoutParams.receiveInsetsIgnoringZOrder = true;
        windowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
        windowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
        windowLayoutParams.privateFlags =
                WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
        return windowLayoutParams;
    }

    public void onConfigurationChanged(@Config int configChanges) {
        mControllers.onConfigurationChanged(configChanges);
    }
@@ -270,14 +278,6 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
        return mViewCache;
    }

    @Override
    public boolean supportsIme() {
        // Currently we don't support IME because we have FLAG_NOT_FOCUSABLE. We can remove that
        // flag when opening a floating view that needs IME (such as Folder), but then that means
        // Taskbar will be below IME and thus users can't click the back button.
        return false;
    }

    @Override
    public View.OnClickListener getItemOnClickListener() {
        return this::onTaskbarIconClicked;
@@ -505,6 +505,31 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
        return mTaskbarHeightForIme;
    }

    /**
     * Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar
     * window. If we're now focusable, also move nav buttons to a separate window above IME.
     */
    public void setTaskbarWindowFocusableForIme(boolean focusable) {
        if (focusable) {
            mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE;
            mControllers.navbarButtonsViewController.moveNavButtonsToNewWindow();
        } else {
            mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE;
            mControllers.navbarButtonsViewController.moveNavButtonsBackToTaskbarWindow();
        }
        mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
    }

    /** Adds the given view to WindowManager with the provided LayoutParams (creates new window). */
    public void addWindowView(View view, WindowManager.LayoutParams windowLayoutParams) {
        mWindowManager.addView(view, windowLayoutParams);
    }

    /** Removes the given view from WindowManager. See {@link #addWindowView}. */
    public void removeWindowView(View view) {
        mWindowManager.removeViewImmediate(view);
    }

    protected void onTaskbarIconClicked(View view) {
        Object tag = view.getTag();
        if (tag instanceof Task) {
@@ -514,6 +539,17 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
        } else if (tag instanceof FolderInfo) {
            FolderIcon folderIcon = (FolderIcon) view;
            Folder folder = folderIcon.getFolder();

            folder.setOnFolderStateChangedListener(newState -> {
                if (newState == Folder.STATE_OPEN) {
                    setTaskbarWindowFocusableForIme(true);
                } else if (newState == Folder.STATE_CLOSED) {
                    // Defer by a frame to ensure we're no longer fullscreen and thus won't jump.
                    getDragLayer().post(() -> setTaskbarWindowFocusableForIme(false));
                    folder.setOnFolderStateChangedListener(null);
                }
            });

            setTaskbarWindowFullscreen(true);

            getDragLayer().post(() -> {
+18 −0
Original line number Diff line number Diff line
@@ -15,17 +15,22 @@
 */
package com.android.launcher3.taskbar;

import static android.view.KeyEvent.ACTION_UP;
import static android.view.KeyEvent.KEYCODE_BACK;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.R;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
@@ -187,4 +192,17 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
        TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
        return super.dispatchTouchEvent(ev);
    }

    /** Called while Taskbar window is focusable, e.g. when pressing back while a folder is open */
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) {
            AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
            if (topView != null && topView.onBackPressed()) {
                // Handled by the floating view.
                return true;
            }
        }
        return super.dispatchKeyEvent(event);
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -229,7 +229,7 @@ public final class Utilities {
            offsetPoints(coord, v.getLeft(), v.getTop());
            scale *= v.getScaleX();

            v = (View) v.getParent();
            v = v.getParent() instanceof View ? (View) v.getParent() : null;
        }
        return scale;
    }
+42 −24
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ import android.view.animation.AnimationUtils;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;

import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;

@@ -101,6 +102,8 @@ import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.views.ClipPathView;
import com.android.launcher3.widget.PendingAddShortcutInfo;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -130,10 +133,13 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
     */
    private static final int MIN_CONTENT_DIMEN = 5;

    static final int STATE_NONE = -1;
    static final int STATE_SMALL = 0;
    static final int STATE_ANIMATING = 1;
    static final int STATE_OPEN = 2;
    public static final int STATE_CLOSED = 0;
    public static final int STATE_ANIMATING = 1;
    public static final int STATE_OPEN = 2;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({STATE_CLOSED, STATE_ANIMATING, STATE_OPEN})
    public @interface FolderState {}

    /**
     * Time for which the scroll hint is shown before automatically changing page.
@@ -198,13 +204,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo

    @ViewDebug.ExportedProperty(category = "launcher",
            mapping = {
                    @ViewDebug.IntToString(from = STATE_NONE, to = "STATE_NONE"),
                    @ViewDebug.IntToString(from = STATE_SMALL, to = "STATE_SMALL"),
                    @ViewDebug.IntToString(from = STATE_CLOSED, to = "STATE_CLOSED"),
                    @ViewDebug.IntToString(from = STATE_ANIMATING, to = "STATE_ANIMATING"),
                    @ViewDebug.IntToString(from = STATE_OPEN, to = "STATE_OPEN"),
            })
    @Thunk
    int mState = STATE_NONE;
    private int mState = STATE_CLOSED;
    private OnFolderStateChangedListener mOnFolderStateChangedListener;
    @ViewDebug.ExportedProperty(category = "launcher")
    private boolean mRearrangeOnClose = false;
    boolean mItemsInvalidated = false;
@@ -277,7 +282,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
        mPageIndicator = findViewById(R.id.folder_page_indicator);
        mFolderName = findViewById(R.id.folder_name);
        mFolderName.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp.folderLabelTextSizePx);
        if (mActivityContext.supportsIme()) {
        mFolderName.setOnBackKeyListener(this);
        mFolderName.setOnFocusChangeListener(this);
        mFolderName.setOnEditorActionListener(this);
@@ -287,9 +291,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
                | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
                | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
        mFolderName.forceDisableSuggestions(true);
        } else {
            mFolderName.setEnabled(false);
        }

        mFooter = findViewById(R.id.folder_footer);
        mFooterHeight = getResources().getDimensionPixelSize(R.dimen.folder_label_height);
@@ -561,7 +562,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
        a.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                mState = STATE_ANIMATING;
                setState(STATE_ANIMATING);
                mCurrentAnimator = a;
            }

@@ -686,7 +687,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo

            @Override
            public void onAnimationEnd(Animator animation) {
                mState = STATE_OPEN;
                setState(STATE_OPEN);
                announceAccessibilityChanges();
                AccessibilityManagerCompat.sendFolderOpenedEventToTest(getContext());

@@ -862,7 +863,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
        }
        mSuppressFolderDeletion = false;
        clearDragInfo();
        mState = STATE_SMALL;
        setState(STATE_CLOSED);
        mContent.setCurrentPage(0);
    }

@@ -1655,4 +1656,21 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo

        return windowBottomPx - folderBottomPx;
    }

    private void setState(@FolderState int newState) {
        mState = newState;
        if (mOnFolderStateChangedListener != null) {
            mOnFolderStateChangedListener.onFolderStateChanged(mState);
        }
    }

    public void setOnFolderStateChangedListener(@Nullable OnFolderStateChangedListener listener) {
        mOnFolderStateChangedListener = listener;
    }

    /** Listener that can be registered via {@link Folder#setOnFolderStateChangedListener} */
    public interface OnFolderStateChangedListener {
        /** See {@link Folder.FolderState} */
        void onFolderStateChanged(@FolderState int newState);
    }
}
Loading