diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java index 002a86c697..094bced31e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java @@ -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 mPropertyHolders = new ArrayList<>(); private final ArrayList 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 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(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 { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 1198dc5b0a..b32137a036 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -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(() -> { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java index b42a60ceea..df004ef6e4 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java @@ -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 { 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); + } } diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index b062b9176f..225460dc98 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -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; } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index daef68226d..1e6342cc8b 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -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,19 +282,15 @@ 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); - mFolderName.setSelectAllOnFocus(true); - mFolderName.setInputType(mFolderName.getInputType() - & ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT - | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS - | InputType.TYPE_TEXT_FLAG_CAP_WORDS); - mFolderName.forceDisableSuggestions(true); - } else { - mFolderName.setEnabled(false); - } + mFolderName.setOnBackKeyListener(this); + mFolderName.setOnFocusChangeListener(this); + mFolderName.setOnEditorActionListener(this); + mFolderName.setSelectAllOnFocus(true); + mFolderName.setInputType(mFolderName.getInputType() + & ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT + | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS + | InputType.TYPE_TEXT_FLAG_CAP_WORDS); + mFolderName.forceDisableSuggestions(true); 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); + } } diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java index ac5368c8b4..a1f31fe459 100644 --- a/src/com/android/launcher3/util/UiThreadHelper.java +++ b/src/com/android/launcher3/util/UiThreadHelper.java @@ -28,7 +28,6 @@ import android.os.Message; import android.view.View; import android.view.inputmethod.InputMethodManager; -import com.android.launcher3.BaseActivity; import com.android.launcher3.views.ActivityContext; /** @@ -56,7 +55,7 @@ public class UiThreadHelper { STATS_LOGGER_KEY, Message.obtain( HANDLER.get(root.getContext()), - () -> BaseActivity.fromContext(root.getContext()) + () -> ActivityContext.lookupContext(root.getContext()) .getStatsLogManager() .logger() .log(LAUNCHER_ALLAPPS_KEYBOARD_CLOSED) diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java index e09eff6f94..c0f6316b95 100644 --- a/src/com/android/launcher3/views/ActivityContext.java +++ b/src/com/android/launcher3/views/ActivityContext.java @@ -124,13 +124,6 @@ public interface ActivityContext { return true; } - /** - * Returns whether we can show the IME for elements hosted by this ActivityContext. - */ - default boolean supportsIme() { - return true; - } - /** * Called just before logging the given item. */