diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java index b4b83f6f24..5d91acd3e1 100644 --- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java +++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java @@ -47,9 +47,9 @@ public class TaskbarAllAppsContainerView extends } @Override - protected View inflateSearchBox() { + protected View inflateSearchBar() { if (isSearchSupported()) { - return super.inflateSearchBox(); + return super.inflateSearchBar(); } // Remove top padding of header, since we do not have any search diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java index 4ac779fdfc..2027bf916c 100644 --- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java +++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java @@ -169,6 +169,13 @@ public final class TaskbarAllAppsController { } private void cleanUpOverlay() { + // Floating search bar is added to the drag layer in ActivityAllAppsContainerView onAttach; + // removed here as this is a special case that we remove the all apps panel. + if (mAppsView != null && mOverlayContext != null + && mAppsView.getSearchUiDelegate().isSearchBarFloating()) { + mOverlayContext.getDragLayer().removeView(mAppsView.getSearchView()); + mAppsView.getSearchUiDelegate().onDestroySearchBar(); + } if (mSearchSessionController != null) { mSearchSessionController.onDestroy(); mSearchSessionController = null; diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java index f6a25ce761..ed0a0d5172 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java @@ -20,6 +20,7 @@ import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAP import android.content.Context; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.R; @@ -104,9 +105,35 @@ public class AllAppsState extends LauncherState { @Override public int getVisibleElements(Launcher launcher) { - // Don't add HOTSEAT_ICONS for non-tablets in ALL_APPS state. - return launcher.getDeviceProfile().isTablet ? ALL_APPS_CONTENT | HOTSEAT_ICONS - : ALL_APPS_CONTENT; + int elements = ALL_APPS_CONTENT | FLOATING_SEARCH_BAR; + // Only add HOTSEAT_ICONS for tablets in ALL_APPS state. + if (launcher.getDeviceProfile().isTablet) { + elements |= HOTSEAT_ICONS; + } + return elements; + } + + @Override + public int getFloatingSearchBarRestingMarginBottom(Launcher launcher) { + return 0; + } + + @Override + public int getFloatingSearchBarRestingMarginStart(Launcher launcher) { + DeviceProfile dp = launcher.getDeviceProfile(); + return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(); + } + + @Override + public int getFloatingSearchBarRestingMarginEnd(Launcher launcher) { + DeviceProfile dp = launcher.getDeviceProfile(); + return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(); + } + + @Override + public boolean shouldFloatingSearchBarUsePillWhenUnfocused(Launcher launcher) { + DeviceProfile dp = launcher.getDeviceProfile(); + return dp.isPhone && !dp.isLandscape; } @Override diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java index 3f0b54e618..396d0abeb0 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -107,7 +107,32 @@ public class OverviewState extends LauncherState { @Override public int getVisibleElements(Launcher launcher) { - return CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS; + int elements = CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS; + DeviceProfile dp = launcher.getDeviceProfile(); + boolean showFloatingSearch; + if (dp.isPhone) { + // Only show search in phone overview in portrait mode. + showFloatingSearch = !dp.isLandscape; + } else { + // Only show search in tablet overview if taskbar is not visible. + showFloatingSearch = !dp.isTaskbarPresent || isTaskbarStashed(launcher); + } + if (showFloatingSearch) { + elements |= FLOATING_SEARCH_BAR; + } + return elements; + } + + @Override + public int getFloatingSearchBarRestingMarginBottom(Launcher launcher) { + return areElementsVisible(launcher, FLOATING_SEARCH_BAR) ? 0 + : super.getFloatingSearchBarRestingMarginBottom(launcher); + } + + @Override + public boolean shouldFloatingSearchBarUsePillWhenUnfocused(Launcher launcher) { + DeviceProfile dp = launcher.getDeviceProfile(); + return dp.isPhone && !dp.isLandscape; } @Override diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 0bf82cf4f1..87fbcaee26 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -811,6 +811,10 @@ public abstract class AbsSwipeUpHandler, VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC); maybeUpdateRecentsAttachedState(true); + if (mActivity != null) { + mActivity.getAppsView().getSearchUiManager().prepareToFocusEditText(mIsInAllAppsRegion); + } + // Draw active task below Launcher so that All Apps can appear over it. runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(isInAllAppsRegion)); diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java index df9830a93f..cb35ec8455 100644 --- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java +++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java @@ -56,7 +56,7 @@ public class AnimatorControllerWithResistance { private enum RecentsResistanceParams { FROM_APP(0.75f, 0.5f, 1f, false), - FROM_APP_TO_ALL_APPS(0.75f, 0.5f, 0.8f, false), + FROM_APP_TO_ALL_APPS(1f, 0.6f, 0.8f, false), FROM_APP_TABLET(1f, 0.7f, 1f, true), FROM_APP_TO_ALL_APPS_TABLET(1f, 0.5f, 0.5f, false), FROM_OVERVIEW(1f, 0.75f, 0.5f, false); diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 22d393a631..d6669ea4f0 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -546,7 +546,9 @@ public class DeviceProfile { overviewTaskIconDrawableSizeGridPx = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_drawable_size_grid); overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx; - overviewActionsTopMarginPx = res.getDimensionPixelSize(R.dimen.overview_actions_top_margin); + // Don't add margin with floating search bar to minimize risk of overlapping. + overviewActionsTopMarginPx = FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() ? 0 + : res.getDimensionPixelSize(R.dimen.overview_actions_top_margin); overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing); overviewActionsButtonSpacing = res.getDimensionPixelSize( R.dimen.overview_actions_button_spacing); @@ -1434,6 +1436,25 @@ public class DeviceProfile { return hotseatBarPadding; } + /** The margin between the edge of all apps and the edge of the first icon. */ + public int getAllAppsIconStartMargin() { + int allAppsSpacing; + if (isVerticalBarLayout()) { + // On phones, the landscape layout uses a different setup. + allAppsSpacing = workspacePadding.left + workspacePadding.right; + } else { + allAppsSpacing = allAppsLeftRightPadding * 2 + allAppsLeftRightMargin * 2; + } + + int cellWidth = DeviceProfile.calculateCellWidth( + availableWidthPx - allAppsSpacing, + 0 /* borderSpace */, + numShownAllAppsColumns); + int iconVisibleSize = Math.round(ICON_VISIBLE_AREA_FACTOR * allAppsIconSizePx); + int iconAlignmentMargin = (cellWidth - iconVisibleSize) / 2; + return allAppsLeftRightPadding + iconAlignmentMargin; + } + private int getAdditionalQsbSpace() { return isQsbInline ? hotseatQsbWidth + hotseatBorderSpace : 0; } diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java index 05471ada48..bfbca657ed 100644 --- a/src/com/android/launcher3/LauncherState.java +++ b/src/com/android/launcher3/LauncherState.java @@ -33,6 +33,7 @@ import static com.android.launcher3.testing.shared.TestProtocol.SPRING_LOADED_ST import android.content.Context; import android.graphics.Color; +import android.view.View; import android.view.animation.Interpolator; import androidx.annotation.FloatRange; @@ -66,6 +67,7 @@ public abstract class LauncherState implements BaseState { public static final int CLEAR_ALL_BUTTON = 1 << 4; public static final int WORKSPACE_PAGE_INDICATOR = 1 << 5; public static final int SPLIT_PLACHOLDER_VIEW = 1 << 6; + public static final int FLOATING_SEARCH_BAR = 1 << 7; // Flag indicating workspace has multiple pages visible. public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0); @@ -202,8 +204,61 @@ public abstract class LauncherState implements BaseState { return 0; } + /** + * How far from the bottom of the screen the floating search bar should rest in this + * state when the IME is not present. + *

+ * To hide offscreen, use a negative value. + *

+ * Note: if the provided value is non-negative but less than the current bottom insets, the + * insets will be applied. As such, you can use 0 to default to this. + */ + public int getFloatingSearchBarRestingMarginBottom(Launcher launcher) { + DeviceProfile dp = launcher.getDeviceProfile(); + return areElementsVisible(launcher, FLOATING_SEARCH_BAR) ? dp.getQsbOffsetY() + : -dp.hotseatQsbHeight; + } + + /** + * How far from the start of the screen the floating search bar should rest. + *

+ * To use original margin, return a negative value. + */ + public int getFloatingSearchBarRestingMarginStart(Launcher launcher) { + boolean isRtl = Utilities.isRtl(launcher.getResources()); + View qsb = launcher.getHotseat().getQsb(); + return isRtl ? launcher.getHotseat().getRight() - qsb.getRight() : qsb.getLeft(); + } + + /** + * How far from the end of the screen the floating search bar should rest. + *

+ * To use original margin, return a negative value. + */ + public int getFloatingSearchBarRestingMarginEnd(Launcher launcher) { + DeviceProfile dp = launcher.getDeviceProfile(); + if (dp.isQsbInline) { + int marginStart = getFloatingSearchBarRestingMarginStart(launcher); + return dp.widthPx - marginStart - dp.hotseatQsbWidth; + } + + boolean isRtl = Utilities.isRtl(launcher.getResources()); + View qsb = launcher.getHotseat().getQsb(); + return isRtl ? qsb.getLeft() : launcher.getHotseat().getRight() - qsb.getRight(); + } + + /** Whether the floating search bar should use the pill UI when not focused. */ + public boolean shouldFloatingSearchBarUsePillWhenUnfocused(Launcher launcher) { + return false; + } + public int getVisibleElements(Launcher launcher) { - return HOTSEAT_ICONS | WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR; + int elements = HOTSEAT_ICONS | WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR; + // Floating search bar is visible in normal state except in landscape on phones. + if (!(launcher.getDeviceProfile().isPhone && launcher.getDeviceProfile().isLandscape)) { + elements |= FLOATING_SEARCH_BAR; + } + return elements; } /** diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java index 898009dead..c7ae2e7e83 100644 --- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java @@ -229,6 +229,10 @@ public class ActivityAllAppsContainerView return new AllAppsSearchUiDelegate(this); } + public AllAppsSearchUiDelegate getSearchUiDelegate() { + return mSearchUiDelegate; + } + /** * Initializes the view hierarchy and internal variables. Any initialization which actually uses * these members should be done in {@link #onFinishInflate()}. @@ -255,11 +259,13 @@ public class ActivityAllAppsContainerView mFastScroller = findViewById(R.id.fast_scroller); mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup)); - // Add the search box above everything else. - mSearchContainer = inflateSearchBox(); - addView(mSearchContainer); + mSearchContainer = inflateSearchBar(); + if (!isSearchBarFloating()) { + // Add the search box above everything else in this container (if the flag is enabled, + // it's added to drag layer in onAttach instead). + addView(mSearchContainer); + } mSearchUiManager = (SearchUiManager) mSearchContainer; - mSearchUiDelegate.onInitializeSearchBox(); } @Override @@ -290,6 +296,13 @@ public class ActivityAllAppsContainerView @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); + if (isSearchBarFloating()) { + // Note: for Taskbar this is removed in TaskbarAllAppsController#cleanUpOverlay when the + // panel is closed. Can't do so in onDetach because we are also a child of drag layer + // so can't remove its views during that dispatch. + mActivityContext.getDragLayer().addView(mSearchContainer); + mSearchUiDelegate.onInitializeSearchBar(); + } mActivityContext.addOnDeviceProfileChangeListener(this); } @@ -311,7 +324,7 @@ public class ActivityAllAppsContainerView * Temporarily force the bottom sheet to be visible on non-tablets. * * @param force {@code true} means bottom sheet will be visible on phones until {@code reset()}. - **/ + */ public void forceBottomSheetVisible(boolean force) { mForceBottomSheetVisible = force; updateBackgroundVisibility(mActivityContext.getDeviceProfile()); @@ -421,7 +434,7 @@ public class ActivityAllAppsContainerView * A-Z apps list. * * @param animate Whether to animate the header during the reset (e.g. switching profile tabs). - **/ + */ public void reset(boolean animate) { reset(animate, true); } @@ -431,7 +444,7 @@ public class ActivityAllAppsContainerView * * @param animate Whether to animate the header during the reset (e.g. switching profile tabs). * @param exitSearch Whether to force exit the search state and return to A-Z apps list. - **/ + */ public void reset(boolean animate, boolean exitSearch) { for (int i = 0; i < mAH.size(); i++) { if (mAH.get(i).mRecyclerView != null) { @@ -494,6 +507,9 @@ public class ActivityAllAppsContainerView // Will be called at the end of the animation. return; } + if (currentActivePage != SEARCH) { + mActivityContext.hideKeyboard(); + } if (mAH.get(currentActivePage).mRecyclerView != null) { mAH.get(currentActivePage).mRecyclerView.bindFastScrollbar(mFastScroller); } @@ -551,7 +567,6 @@ public class ActivityAllAppsContainerView mActivityContext.getStatsLogManager().logger() .log(LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB); } - mActivityContext.hideKeyboard(); }); findViewById(R.id.tab_work) .setOnClickListener((View view) -> { @@ -559,24 +574,24 @@ public class ActivityAllAppsContainerView mActivityContext.getStatsLogManager().logger() .log(LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB); } - mActivityContext.hideKeyboard(); }); setDeviceManagementResources(); - onActivePageChanged(mViewPager.getNextPage()); + if (mHeader.isSetUp()) { + onActivePageChanged(mViewPager.getNextPage()); + } } else { mAH.get(AdapterHolder.MAIN).setup(findViewById(R.id.apps_list_view), null); mAH.get(AdapterHolder.WORK).mRecyclerView = null; } setupHeader(); - if (isSearchBarOnBottom()) { + if (isSearchBarFloating()) { // Keep the scroller above the search bar. RelativeLayout.LayoutParams scrollerLayoutParams = (LayoutParams) mFastScroller.getLayoutParams(); - scrollerLayoutParams.addRule(RelativeLayout.ABOVE, R.id.search_container_all_apps); - scrollerLayoutParams.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM); - scrollerLayoutParams.bottomMargin = getResources().getDimensionPixelSize( - R.dimen.fastscroll_bottom_margin_floating_search); + scrollerLayoutParams.bottomMargin = mSearchContainer.getHeight() + + getResources().getDimensionPixelSize( + R.dimen.fastscroll_bottom_margin_floating_search); } mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.MAIN).mRecyclerView); @@ -628,11 +643,9 @@ public class ActivityAllAppsContainerView removeCustomRules(getSearchRecyclerView()); if (!isSearchSupported()) { layoutWithoutSearchContainer(rvContainer, showTabs); - } else if (isSearchBarOnBottom()) { + } else if (isSearchBarFloating()) { alignParentTop(rvContainer, showTabs); alignParentTop(getSearchRecyclerView(), /* tabs= */ false); - layoutAboveSearchContainer(rvContainer); - layoutAboveSearchContainer(getSearchRecyclerView()); } else { layoutBelowSearchContainer(rvContainer, showTabs); layoutBelowSearchContainer(getSearchRecyclerView(), /* tabs= */ false); @@ -663,7 +676,7 @@ public class ActivityAllAppsContainerView removeCustomRules(mHeader); if (!isSearchSupported()) { layoutWithoutSearchContainer(mHeader, false /* includeTabsMargin */); - } else if (isSearchBarOnBottom()) { + } else if (isSearchBarFloating()) { alignParentTop(mHeader, false /* includeTabsMargin */); } else { layoutBelowSearchContainer(mHeader, false /* includeTabsMargin */); @@ -703,16 +716,62 @@ public class ActivityAllAppsContainerView } /** - * It is up to the search container view created by {@link #inflateSearchBox()} to use the - * floating search bar flag to move itself to the bottom of this container. This method checks - * if that had been done; otherwise the flag will be ignored. - * - * @return true if the search bar is at the bottom of the container (as opposed to the top). - **/ - private boolean isSearchBarOnBottom() { - return FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() - && ((RelativeLayout.LayoutParams) mSearchContainer.getLayoutParams()).getRule( - ALIGN_PARENT_BOTTOM) == RelativeLayout.TRUE; + * @return true if the search bar is floating above this container (at the bottom of the screen) + */ + protected boolean isSearchBarFloating() { + return mSearchUiDelegate.isSearchBarFloating(); + } + + /** + * Whether the floating search bar should appear as a small pill when not focused. + *

+ * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely + * makes sense to use that method to derive an appropriate value for the current/target state. + */ + public boolean shouldFloatingSearchBarBePillWhenUnfocused() { + return false; + } + + /** + * How far from the bottom of the screen the floating search bar should rest when the + * IME is not present. + *

+ * To hide offscreen, use a negative value. + *

+ * Note: if the provided value is non-negative but less than the current bottom insets, the + * insets will be applied. As such, you can use 0 to default to this. + *

+ * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely + * makes sense to use that method to derive an appropriate value for the current/target state. + */ + public int getFloatingSearchBarRestingMarginBottom() { + return 0; + } + + /** + * How far from the start of the screen the floating search bar should rest. + *

+ * To use original margin, return a negative value. + *

+ * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely + * makes sense to use that method to derive an appropriate value for the current/target state. + */ + public int getFloatingSearchBarRestingMarginStart() { + DeviceProfile dp = mActivityContext.getDeviceProfile(); + return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(); + } + + /** + * How far from the end of the screen the floating search bar should rest. + *

+ * To use original margin, return a negative value. + *

+ * Note: This method mirrors one in LauncherState. For subclasses that use Launcher, it likely + * makes sense to use that method to derive an appropriate value for the current/target state. + */ + public int getFloatingSearchBarRestingMarginEnd() { + DeviceProfile dp = mActivityContext.getDeviceProfile(); + return dp.allAppsLeftRightMargin + dp.getAllAppsIconStartMargin(); } private void layoutBelowSearchContainer(View v, boolean includeTabsMargin) { @@ -732,15 +791,6 @@ public class ActivityAllAppsContainerView layoutParams.topMargin = topMargin; } - private void layoutAboveSearchContainer(View v) { - if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) { - return; - } - - RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams(); - layoutParams.addRule(RelativeLayout.ABOVE, R.id.search_container_all_apps); - } - private void alignParentTop(View v, boolean includeTabsMargin) { if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) { return; @@ -794,10 +844,10 @@ public class ActivityAllAppsContainerView } /** - * Inflates the search box + * Inflates the search bar */ - protected View inflateSearchBox() { - return mSearchUiDelegate.inflateSearchBox(); + protected View inflateSearchBar() { + return mSearchUiDelegate.inflateSearchBar(); } /** The adapter provider for the main section. */ @@ -977,7 +1027,7 @@ public class ActivityAllAppsContainerView /** * The container for A-Z apps (the ViewPager for main+work tabs, or main RV). This is currently * hidden while searching. - **/ + */ public ViewGroup getAppsRecyclerViewContainer() { return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view); } @@ -1024,7 +1074,7 @@ public class ActivityAllAppsContainerView setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0); } else { int topPadding = grid.allAppsTopPadding; - if (isSearchBarOnBottom() && !grid.isTablet) { + if (isSearchBarFloating() && !grid.isTablet) { topPadding += getResources().getDimensionPixelSize( R.dimen.all_apps_additional_top_padding_floating_search); } @@ -1236,7 +1286,7 @@ public class ActivityAllAppsContainerView final FloatingHeaderView headerView = getFloatingHeaderView(); if (hasBottomSheet) { // Start adding header protection if search bar or tabs will attach to the top. - if (!FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() || mUsingTabs) { + if (!isSearchBarFloating() || mUsingTabs) { mTmpRectF.set( leftWithScale, topWithScale, @@ -1292,7 +1342,7 @@ public class ActivityAllAppsContainerView /** Returns the position of the bottom edge of the header */ public int getHeaderBottom() { int bottom = (int) getTranslationY() + mHeader.getClipTop(); - if (isSearchBarOnBottom()) { + if (isSearchBarFloating()) { if (mActivityContext.getDeviceProfile().isTablet) { return bottom + mBottomSheetBackground.getTop(); } @@ -1363,6 +1413,9 @@ public class ActivityAllAppsContainerView if (isWork() && mWorkManager.getWorkModeSwitch() != null) { bottomOffset = mInsets.bottom + mWorkManager.getWorkModeSwitch().getHeight(); } + if (isSearchBarFloating()) { + bottomOffset += mSearchContainer.getHeight(); + } mRecyclerView.setPadding(mPadding.left, mPadding.top, mPadding.right, mPadding.bottom + bottomOffset); } diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 6ca084a3b7..0d7b736cc8 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -22,6 +22,7 @@ import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT; +import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER; import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_BOTTOM_SHEET_FADE; @@ -234,7 +235,11 @@ public class AllAppsTransitionController */ public void setProgress(float progress) { mProgress = progress; - getAppsViewProgressTranslationY().setValue(mProgress * mShiftRange); + boolean fromBackground = + mLauncher.getStateManager().getCurrentStableState() == BACKGROUND_APP; + // Allow apps panel to shift the full screen if coming from another app. + float shiftRange = fromBackground ? mLauncher.getDeviceProfile().heightPx : mShiftRange; + getAppsViewProgressTranslationY().setValue(mProgress * shiftRange); mLauncher.onAllAppsTransition(1 - progress); boolean hasScrim = progress < NAV_BAR_COLOR_FORCE_UPDATE_THRESHOLD diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java index aefedae641..d78e453218 100644 --- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java @@ -22,6 +22,7 @@ import android.view.WindowInsets; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.Utilities; +import com.android.launcher3.statemanager.StateManager; /** * AllAppsContainerView with launcher specific callbacks @@ -53,4 +54,75 @@ public class LauncherAllAppsContainerView extends ActivityAllAppsContainerView manager = launcher.getStateManager(); + if (manager.isInTransition() && manager.getTargetState() != null) { + return manager.getTargetState().shouldFloatingSearchBarUsePillWhenUnfocused(launcher); + } + return manager.getCurrentStableState() + .shouldFloatingSearchBarUsePillWhenUnfocused(launcher); + } + + @Override + public int getFloatingSearchBarRestingMarginBottom() { + if (!isSearchBarFloating()) { + return super.getFloatingSearchBarRestingMarginBottom(); + } + Launcher launcher = mActivityContext; + StateManager stateManager = launcher.getStateManager(); + + // We want to rest at the current state's resting position, unless we are in transition and + // the target state's resting position is higher (that way if we are closing the keyboard, + // we can stop translating at that point). + int currentStateMarginBottom = stateManager.getCurrentStableState() + .getFloatingSearchBarRestingMarginBottom(launcher); + int targetStateMarginBottom = -1; + if (stateManager.isInTransition() && stateManager.getTargetState() != null) { + targetStateMarginBottom = stateManager.getTargetState() + .getFloatingSearchBarRestingMarginBottom(launcher); + if (targetStateMarginBottom < 0) { + // Go ahead and move offscreen. + return targetStateMarginBottom; + } + } + return Math.max(targetStateMarginBottom, currentStateMarginBottom); + } + + @Override + public int getFloatingSearchBarRestingMarginStart() { + if (!isSearchBarFloating()) { + return super.getFloatingSearchBarRestingMarginStart(); + } + + StateManager stateManager = mActivityContext.getStateManager(); + + if (stateManager.isInTransition() && stateManager.getTargetState() != null) { + return stateManager.getTargetState() + .getFloatingSearchBarRestingMarginStart(mActivityContext); + } + return stateManager.getCurrentStableState() + .getFloatingSearchBarRestingMarginStart(mActivityContext); + } + + @Override + public int getFloatingSearchBarRestingMarginEnd() { + if (!isSearchBarFloating()) { + return super.getFloatingSearchBarRestingMarginEnd(); + } + + StateManager stateManager = mActivityContext.getStateManager(); + + if (stateManager.isInTransition() && stateManager.getTargetState() != null) { + return stateManager.getTargetState() + .getFloatingSearchBarRestingMarginEnd(mActivityContext); + } + return stateManager.getCurrentStableState() + .getFloatingSearchBarRestingMarginEnd(mActivityContext); + } } diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java index 2174936654..bfd89678df 100644 --- a/src/com/android/launcher3/allapps/SearchUiManager.java +++ b/src/com/android/launcher3/allapps/SearchUiManager.java @@ -48,6 +48,14 @@ public interface SearchUiManager { @Nullable ExtendedEditText getEditText(); + /** + * Hint to the edit text that it is about to be focused or unfocused. This can be used to start + * animating the edit box accordingly, e.g. after a gesture completes. + * + * @param focused true if the edit text is about to be focused, false if it will be unfocused + */ + default void prepareToFocusEditText(boolean focused) {} + /** * Sets whether EditText background should be visible * @param maxAlpha defines the maximum alpha the background should animates to diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java index 8c2fb195c6..e60752e176 100644 --- a/src/com/android/launcher3/allapps/WorkModeSwitch.java +++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java @@ -36,7 +36,6 @@ import com.android.launcher3.Insettable; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.KeyboardInsetAnimationCallback; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.StringCache; import com.android.launcher3.views.ActivityContext; @@ -108,7 +107,7 @@ public class WorkModeSwitch extends LinearLayout implements Insettable, if (lp != null) { int bottomMargin = getResources().getDimensionPixelSize(R.dimen.work_fab_margin_bottom); DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile(); - if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) { + if (mActivityContext.getAppsView().isSearchBarFloating()) { bottomMargin += dp.hotseatQsbHeight; } diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchUiDelegate.java b/src/com/android/launcher3/allapps/search/AllAppsSearchUiDelegate.java index abec16e45c..49cecca19f 100644 --- a/src/com/android/launcher3/allapps/search/AllAppsSearchUiDelegate.java +++ b/src/com/android/launcher3/allapps/search/AllAppsSearchUiDelegate.java @@ -49,8 +49,13 @@ public class AllAppsSearchUiDelegate { // Do nothing. } - /** Invoked when the search box has been added to All Apps. */ - public void onInitializeSearchBox() { + /** Invoked when the search bar has been added to All Apps. */ + public void onInitializeSearchBar() { + // Do nothing. + } + + /** Invoked when the search bar has been removed from All Apps. */ + public void onDestroySearchBar() { // Do nothing. } @@ -59,11 +64,16 @@ public class AllAppsSearchUiDelegate { return LayoutInflater.from(mAppsView.getContext()); } - /** Inflate the search box for All Apps. */ - public View inflateSearchBox() { + /** Inflate the search bar for All Apps. */ + public View inflateSearchBar() { return getLayoutInflater().inflate(R.layout.search_container_all_apps, mAppsView, false); } + /** Whether the search box is floating above the apps surface (inset by the IME). */ + public boolean isSearchBarFloating() { + return false; + } + /** Creates the adapter provider for the main section. */ public SearchAdapterProvider createMainAdapterProvider() { return new DefaultSearchAdapterProvider(mActivityContext); diff --git a/src/com/android/launcher3/anim/KeyboardInsetAnimationCallback.java b/src/com/android/launcher3/anim/KeyboardInsetAnimationCallback.java index b911928e36..39386fa2ac 100644 --- a/src/com/android/launcher3/anim/KeyboardInsetAnimationCallback.java +++ b/src/com/android/launcher3/anim/KeyboardInsetAnimationCallback.java @@ -44,14 +44,30 @@ public class KeyboardInsetAnimationCallback extends WindowInsetsAnimation.Callba private float mInitialTranslation; private float mTerminalTranslation; + private KeyboardTranslationState mKeyboardTranslationState = KeyboardTranslationState.SYSTEM; + + /** Current state of the keyboard. */ + public enum KeyboardTranslationState { + // We are not controlling the keyboard, and it may or may not be translating. + SYSTEM, + // We are about to gain control of the keyboard, but the current state may be transient. + MANUAL_PREPARED, + // We are manually translating the keyboard. + MANUAL_ONGOING + } public KeyboardInsetAnimationCallback(View view) { super(DISPATCH_MODE_STOP); mView = view; } + public KeyboardTranslationState getKeyboardTranslationState() { + return mKeyboardTranslationState; + } + @Override public void onPrepare(WindowInsetsAnimation animation) { + mKeyboardTranslationState = KeyboardTranslationState.MANUAL_PREPARED; mInitialTranslation = mView.getTranslationY(); } @@ -62,6 +78,7 @@ public class KeyboardInsetAnimationCallback extends WindowInsetsAnimation.Callba mTerminalTranslation = mView.getTranslationY(); // Reset the translation in case the view is drawn before onProgress gets called. mView.setTranslationY(mInitialTranslation); + mKeyboardTranslationState = KeyboardTranslationState.MANUAL_ONGOING; if (mView instanceof KeyboardInsetListener) { ((KeyboardInsetListener) mView).onTranslationStart(); } @@ -90,6 +107,10 @@ public class KeyboardInsetAnimationCallback extends WindowInsetsAnimation.Callba mView.setTranslationY(translationY); } + if (mView instanceof KeyboardInsetListener) { + ((KeyboardInsetListener) mView).onKeyboardAlphaChanged(animation.getAlpha()); + } + return windowInsets; } @@ -98,7 +119,7 @@ public class KeyboardInsetAnimationCallback extends WindowInsetsAnimation.Callba if (mView instanceof KeyboardInsetListener) { ((KeyboardInsetListener) mView).onTranslationEnd(); } - super.onEnd(animation); + mKeyboardTranslationState = KeyboardTranslationState.SYSTEM; } /** @@ -110,6 +131,13 @@ public class KeyboardInsetAnimationCallback extends WindowInsetsAnimation.Callba */ void onTranslationStart(); + /** + * Called from {@link KeyboardInsetAnimationCallback#onProgress} + * + * @param alpha the current IME alpha + */ + default void onKeyboardAlphaChanged(float alpha) {} + /** * Called from {@link KeyboardInsetAnimationCallback#onEnd} */ diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index e4a9d58280..d4fe7ae82b 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -116,7 +116,8 @@ public final class FeatureFlags { // TODO(Block 4): Cleanup flags public static final BooleanFlag ENABLE_FLOATING_SEARCH_BAR = getReleaseFlag(268388460, "ENABLE_FLOATING_SEARCH_BAR", DISABLED, - "Keep All Apps search bar at the bottom (but above keyboard if open)"); + "Allow search bar to persist and animate across states, and attach to" + + " the keyboard from the bottom of the screen"); public static final BooleanFlag ENABLE_ALL_APPS_FROM_OVERVIEW = getDebugFlag(275132633, "ENABLE_ALL_APPS_FROM_OVERVIEW", DISABLED, diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java index 198dad3c89..b1586dcd5d 100644 --- a/src/com/android/launcher3/statemanager/StateManager.java +++ b/src/com/android/launcher3/statemanager/StateManager.java @@ -76,6 +76,10 @@ public class StateManager> { return mState; } + public STATE_TYPE getTargetState() { + return (STATE_TYPE) mConfig.targetState; + } + public STATE_TYPE getCurrentStableState() { return mCurrentStableState; } diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java index d1e816bec6..0d9e01035e 100644 --- a/src/com/android/launcher3/states/StateAnimationConfig.java +++ b/src/com/android/launcher3/states/StateAnimationConfig.java @@ -66,7 +66,8 @@ public class StateAnimationConfig { ANIM_WORKSPACE_PAGE_TRANSLATE_X, ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN, ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE, - ANIM_ALL_APPS_BOTTOM_SHEET_FADE + ANIM_ALL_APPS_BOTTOM_SHEET_FADE, + ANIM_ALL_APPS_KEYBOARD_FADE }) @Retention(RetentionPolicy.SOURCE) public @interface AnimType {} @@ -90,8 +91,9 @@ public class StateAnimationConfig { public static final int ANIM_OVERVIEW_SPLIT_SELECT_FLOATING_TASK_TRANSLATE_OFFSCREEN = 17; public static final int ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE = 18; public static final int ANIM_ALL_APPS_BOTTOM_SHEET_FADE = 19; + public static final int ANIM_ALL_APPS_KEYBOARD_FADE = 20; - private static final int ANIM_TYPES_COUNT = 20; + private static final int ANIM_TYPES_COUNT = 21; protected final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT]; diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java index b672bde45d..ad812f0a7d 100644 --- a/src/com/android/launcher3/touch/AllAppsSwipeController.java +++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java @@ -22,10 +22,12 @@ import static com.android.app.animation.Interpolators.FINAL_FRAME; import static com.android.app.animation.Interpolators.INSTANT; import static com.android.app.animation.Interpolators.LINEAR; import static com.android.app.animation.Interpolators.clampToProgress; +import static com.android.app.animation.Interpolators.mapToProgress; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_BOTTOM_SHEET_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_KEYBOARD_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH; import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE; @@ -293,20 +295,15 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { config.setInterpolator(ANIM_WORKSPACE_SCALE, INSTANT); config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, INSTANT); } else { - // Remove scrim for this transition. - config.setInterpolator(ANIM_SCRIM_FADE, progress -> 0); - - // For now, pop the background panel in at full opacity at the threshold. + // Pop the background panel, keyboard, and content in at full opacity at the threshold. config.setInterpolator(ANIM_ALL_APPS_BOTTOM_SHEET_FADE, thresholdInterpolator(threshold, INSTANT)); - - // Fade the apps in when the scrim normally does, so it's apparent sooner what is - // happening (in this case we are fading them on top of the background panel). - config.setInterpolator(ANIM_ALL_APPS_FADE, - thresholdInterpolator(threshold, SCRIM_FADE_MANUAL)); + config.setInterpolator(ANIM_ALL_APPS_KEYBOARD_FADE, + thresholdInterpolator(threshold, INSTANT)); + config.setInterpolator(ANIM_ALL_APPS_FADE, thresholdInterpolator(threshold, INSTANT)); config.setInterpolator(ANIM_VERTICAL_PROGRESS, - thresholdInterpolator(threshold, ALL_APPS_VERTICAL_PROGRESS_MANUAL)); + thresholdInterpolator(threshold, mapToProgress(LINEAR, threshold, 1f))); } }