From ef7f874a889b609bd34e692b9c9a1f8cefd1ea95 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Thu, 4 Jun 2015 17:18:17 -0700 Subject: Refactoring all apps search to support external search bar. - Adding support for an external search bar that can be used to search a container view. This adds a new interface AllAppsSearchController which manages the external search bar. Each controller will have its own search implementation which means that we no longer need a common AppSearchManager interface. - Removing elevation controller as we no longer have a builtin search bar in all apps - Refactoring container view insets so that they behave the same in all containers. - Refactoring apps view to ensure that we only update the number of columns with the available width - Cleaning up LauncherCallbacks interface Bug: 20127840 Bug: 21494973 Change-Id: I710b8e18196961d77d8a29f0c345531d480936fe --- res/drawable/all_apps_search_bg.xml | 11 +- res/layout-sw600dp/all_apps.xml | 33 -- res/layout/all_apps.xml | 40 +- res/layout/all_apps_container.xml | 80 +-- res/layout/all_apps_reveal.xml | 24 - res/layout/all_apps_search_bar.xml | 72 +++ res/layout/widgets_view.xml | 22 +- res/values-sw600dp/dimens.xml | 1 - res/values/dimens.xml | 9 +- src/com/android/launcher3/BaseContainerView.java | 96 +-- src/com/android/launcher3/BaseRecyclerView.java | 13 +- src/com/android/launcher3/DeviceProfile.java | 18 +- src/com/android/launcher3/Launcher.java | 127 +--- src/com/android/launcher3/LauncherCallbacks.java | 15 +- src/com/android/launcher3/LauncherExtension.java | 18 +- .../LauncherStateTransitionAnimation.java | 70 ++- .../android/launcher3/LauncherTransitionable.java | 3 - src/com/android/launcher3/Workspace.java | 9 +- .../WorkspaceStateTransitionAnimation.java | 257 +++++--- .../launcher3/allapps/AllAppsContainerView.java | 644 +++++++-------------- .../launcher3/allapps/AllAppsGridAdapter.java | 35 +- .../launcher3/allapps/AllAppsRecyclerView.java | 23 +- .../allapps/AllAppsSearchBarController.java | 97 ++++ .../launcher3/allapps/AlphabeticalAppsList.java | 21 +- .../launcher3/allapps/AppSearchManager.java | 59 -- .../allapps/DefaultAppSearchAlgorithm.java | 91 +++ .../allapps/DefaultAppSearchController.java | 270 +++++++++ .../allapps/SimpleAppSearchManagerImpl.java | 98 ---- .../launcher3/widget/WidgetsContainerView.java | 42 +- .../launcher3/widget/WidgetsRecyclerView.java | 7 - 30 files changed, 1164 insertions(+), 1141 deletions(-) delete mode 100644 res/layout-sw600dp/all_apps.xml delete mode 100644 res/layout/all_apps_reveal.xml create mode 100644 res/layout/all_apps_search_bar.xml create mode 100644 src/com/android/launcher3/allapps/AllAppsSearchBarController.java delete mode 100644 src/com/android/launcher3/allapps/AppSearchManager.java create mode 100644 src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java create mode 100644 src/com/android/launcher3/allapps/DefaultAppSearchController.java delete mode 100644 src/com/android/launcher3/allapps/SimpleAppSearchManagerImpl.java diff --git a/res/drawable/all_apps_search_bg.xml b/res/drawable/all_apps_search_bg.xml index 57eb5825e..a09f88fd4 100644 --- a/res/drawable/all_apps_search_bg.xml +++ b/res/drawable/all_apps_search_bg.xml @@ -14,10 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - \ No newline at end of file + \ No newline at end of file diff --git a/res/layout-sw600dp/all_apps.xml b/res/layout-sw600dp/all_apps.xml deleted file mode 100644 index 368e6abdd..000000000 --- a/res/layout-sw600dp/all_apps.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml index b907c34f1..1bf54eefb 100644 --- a/res/layout/all_apps.xml +++ b/res/layout/all_apps.xml @@ -21,15 +21,37 @@ android:id="@+id/apps_view" android:layout_width="match_parent" android:layout_height="match_parent" - android:descendantFocusability="afterDescendants"> - + + + - + + + android:layout_height="0dp" + android:layout_weight="1"> + + + \ No newline at end of file diff --git a/res/layout/all_apps_container.xml b/res/layout/all_apps_container.xml index fc77cd31f..5801a0e8e 100644 --- a/res/layout/all_apps_container.xml +++ b/res/layout/all_apps_container.xml @@ -14,95 +14,35 @@ See the License for the specific language governing permissions and limitations under the License. --> + + android:focusable="true" + android:focusableInTouchMode="true"> + android:focusable="true" + android:descendantFocusability="afterDescendants" /> - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/all_apps_reveal.xml b/res/layout/all_apps_reveal.xml deleted file mode 100644 index 5f4665642..000000000 --- a/res/layout/all_apps_reveal.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - \ No newline at end of file diff --git a/res/layout/all_apps_search_bar.xml b/res/layout/all_apps_search_bar.xml new file mode 100644 index 000000000..9f4e3228c --- /dev/null +++ b/res/layout/all_apps_search_bar.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/widgets_view.xml b/res/layout/widgets_view.xml index a1a62b32a..c27e79bbd 100644 --- a/res/layout/widgets_view.xml +++ b/res/layout/widgets_view.xml @@ -21,25 +21,29 @@ android:id="@+id/widgets_view" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingTop="@dimen/widget_container_inset" - android:paddingBottom="@dimen/widget_container_inset" + android:orientation="vertical" android:descendantFocusability="afterDescendants"> + android:layout_height="match_parent"> + + \ No newline at end of file diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml index daa98ef0b..2651fbb3f 100644 --- a/res/values-sw600dp/dimens.xml +++ b/res/values-sw600dp/dimens.xml @@ -16,7 +16,6 @@ - 18dp 0dp 26sp 12dp diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 2184eadb3..0311a8977 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -51,17 +51,18 @@ - 8dp + 8dp + + 4dp - 8dp 56dp 8dp 24sp - 52dp + 48dp 8dp 8dp 24dp - 16dp + 16dp 4dp -16dp diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java index bd1c625e3..4b7b97775 100644 --- a/src/com/android/launcher3/BaseContainerView.java +++ b/src/com/android/launcher3/BaseContainerView.java @@ -19,16 +19,25 @@ package com.android.launcher3; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; -import android.widget.FrameLayout; +import android.widget.LinearLayout; /** * A base container view, which supports resizing. */ -public class BaseContainerView extends FrameLayout implements Insettable { +public abstract class BaseContainerView extends LinearLayout implements Insettable { - protected Rect mInsets = new Rect(); - protected Rect mFixedBounds = new Rect(); - protected int mFixedBoundsContainerInset; + // The window insets + private Rect mInsets = new Rect(); + // The bounds of the search bar. Only the left, top, right are used to inset the + // search bar and the height is determined by the measurement of the layout + private Rect mSearchBarBounds = new Rect(); + // The bounds of the container + protected Rect mContentBounds = new Rect(); + // The padding to apply to the container to achieve the bounds + protected Rect mContentPadding = new Rect(); + // The inset to apply to the edges and between the search bar and the container + private int mContainerBoundsInset; + private boolean mHasSearchBar; public BaseContainerView(Context context) { this(context, null); @@ -40,62 +49,73 @@ public class BaseContainerView extends FrameLayout implements Insettable { public BaseContainerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - mFixedBoundsContainerInset = context.getResources().getDimensionPixelSize( - R.dimen.container_fixed_bounds_inset); + mContainerBoundsInset = getResources().getDimensionPixelSize(R.dimen.container_bounds_inset); } @Override final public void setInsets(Rect insets) { mInsets.set(insets); - onUpdateBackgrounds(); - onUpdatePaddings(); + updateBackgroundAndPaddings(); + } + + protected void setHasSearchBar() { + mHasSearchBar = true; } /** - * Sets the fixed bounds for this container view. + * Sets the search bar bounds for this container view to match. */ - final public void setFixedBounds(Rect fixedBounds) { - if (!fixedBounds.isEmpty() && !fixedBounds.equals(mFixedBounds)) { - mFixedBounds.set(fixedBounds); - if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) { - mFixedBounds.top = mInsets.top; - mFixedBounds.bottom = mInsets.bottom; - } - // To ensure that the child RecyclerView has the full width to handle touches right to - // the edge of the screen, we only apply the top and bottom padding to the bounds - mFixedBounds.top += mFixedBoundsContainerInset; - mFixedBounds.bottom += mFixedBoundsContainerInset; - onFixedBoundsUpdated(); - } + final public void setSearchBarBounds(Rect bounds) { + mSearchBarBounds.set(bounds); + // Post the updates since they can trigger a relayout, and this call can be triggered from // a layout pass itself. post(new Runnable() { @Override public void run() { - onUpdateBackgrounds(); - onUpdatePaddings(); + updateBackgroundAndPaddings(); } }); } /** - * Update the UI in response to a change in the fixed bounds. + * Update the backgrounds and padding in response to a change in the bounds or insets. */ - protected void onFixedBoundsUpdated() { - // Do nothing - } + protected void updateBackgroundAndPaddings() { + Rect padding; + Rect searchBarBounds = new Rect(mSearchBarBounds); + if (mSearchBarBounds.isEmpty()) { + // Use the normal bounds + padding = new Rect(mInsets.left + mContainerBoundsInset, + (mHasSearchBar ? 0 : (mInsets.top + mContainerBoundsInset)), + mInsets.right + mContainerBoundsInset, + mInsets.bottom + mContainerBoundsInset); - /** - * Update the paddings in response to a change in the bounds or insets. - */ - protected void onUpdatePaddings() { - // Do nothing + // Special case -- we have the search bar, but no specific bounds, so just give it + // the inset bounds without a height. + searchBarBounds.set(mInsets.left + mContainerBoundsInset, + mInsets.top + mContainerBoundsInset, + getMeasuredWidth() - (mInsets.right + mContainerBoundsInset), 0); + } else { + // Use the search bounds, if there is a search bar, the bounds will contain + // the offsets for the insets so we can ignore that + padding = new Rect(mSearchBarBounds.left, + (mHasSearchBar ? 0 : (mInsets.top + mContainerBoundsInset)), + getMeasuredWidth() - mSearchBarBounds.right, + mInsets.bottom + mContainerBoundsInset); + } + if (!padding.equals(mContentPadding) || !searchBarBounds.equals(mSearchBarBounds)) { + mContentPadding.set(padding); + mContentBounds.set(padding.left, padding.top, + getMeasuredWidth() - padding.right, + getMeasuredHeight() - padding.bottom); + mSearchBarBounds.set(searchBarBounds); + onUpdateBackgroundAndPaddings(mSearchBarBounds, padding); + } } /** - * Update the backgrounds in response to a change in the bounds or insets. + * To be implemented by container views to update themselves when the bounds changes. */ - protected void onUpdateBackgrounds() { - // Do nothing - } + protected abstract void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding); } \ No newline at end of file diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java index a207d9a12..081c4f502 100644 --- a/src/com/android/launcher3/BaseRecyclerView.java +++ b/src/com/android/launcher3/BaseRecyclerView.java @@ -89,9 +89,7 @@ public class BaseRecyclerView extends RecyclerView private int mLastY; private int mScrollbarWidth; private int mScrollbarInset; - private Rect mBackgroundPadding = new Rect(); - - + protected Rect mBackgroundPadding = new Rect(); public BaseRecyclerView(Context context) { this(context, null); @@ -230,6 +228,10 @@ public class BaseRecyclerView extends RecyclerView return false; } + public void updateBackgroundPadding(Rect padding) { + mBackgroundPadding.set(padding); + } + @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); @@ -340,9 +342,10 @@ public class BaseRecyclerView extends RecyclerView // Calculate the position for the fast scroller popup Rect bgBounds = mFastScrollerBg.getBounds(); if (Utilities.isRtl(getResources())) { - x = mBackgroundPadding.left + getScrollBarSize(); + x = mBackgroundPadding.left + (2 * getScrollbarWidth()); } else { - x = getWidth() - getPaddingRight() - getScrollBarSize() - bgBounds.width(); + x = getWidth() - mBackgroundPadding.right - (2 * getScrollbarWidth()) - + bgBounds.width(); } y = mLastY - (int) (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * bgBounds.height()); y = Math.max(getPaddingTop(), Math.min(y, getHeight() - getPaddingBottom() - diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 9c59dab53..a50540d26 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -223,26 +223,22 @@ public class DeviceProfile { folderCellHeightPx = cellHeightPx + edgeMarginPx; folderBackgroundOffset = -edgeMarginPx; folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset; - - updateAppsViewNumCols(res, 0); } - public boolean updateAppsViewNumCols(Resources res, int containerWidth) { + /** + * @param recyclerViewWidth the available width of the AllAppsRecyclerView + */ + public void updateAppsViewNumCols(Resources res, int recyclerViewWidth) { int appsViewLeftMarginPx = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin); int allAppsCellWidthGap = res.getDimensionPixelSize(R.dimen.all_apps_icon_width_gap); - int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx; + int availableAppsWidthPx = (recyclerViewWidth > 0) ? recyclerViewWidth : availableWidthPx; int numAppsCols = (availableAppsWidthPx - appsViewLeftMarginPx) / (allAppsIconSizePx + allAppsCellWidthGap); int numPredictiveAppCols = Math.max(inv.minAllAppsPredictionColumns, numAppsCols); - if ((numAppsCols != allAppsNumCols) || - (numPredictiveAppCols != allAppsNumPredictiveCols)) { - allAppsNumCols = numAppsCols; - allAppsNumPredictiveCols = numPredictiveAppCols; - return true; - } - return false; + allAppsNumCols = numAppsCols; + allAppsNumPredictiveCols = numPredictiveAppCols; } /** Returns the search bar top offset */ diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 796de3fa0..335a77bcf 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -99,7 +99,7 @@ import android.widget.Toast; import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.PagedView.PageSwitchListener; import com.android.launcher3.allapps.AllAppsContainerView; -import com.android.launcher3.allapps.AppSearchManager; +import com.android.launcher3.allapps.AllAppsSearchBarController; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherActivityInfoCompat; import com.android.launcher3.compat.LauncherAppsCompat; @@ -138,9 +138,6 @@ public class Launcher extends Activity static final String TAG = "Launcher"; static final boolean LOGD = false; - // Temporary flag - static final boolean DISABLE_ALL_APPS_SEARCH_INTEGRATION = true; - static final boolean PROFILE_STARTUP = false; static final boolean DEBUG_WIDGETS = true; static final boolean DEBUG_STRICT_MODE = false; @@ -573,32 +570,6 @@ public class Launcher extends Activity public boolean setLauncherCallbacks(LauncherCallbacks callbacks) { mLauncherCallbacks = callbacks; - mLauncherCallbacks.setLauncherAppsCallback(new Launcher.LauncherAppsCallbacks() { - @Override - public void onAllAppsBoundsChanged(Rect bounds) { - if (LOGD) { - Log.d(TAG, "onAllAppsBoundsChanged(Rect): " + bounds); - } - mAppsView.setFixedBounds(bounds); - mWidgetsView.setFixedBounds(bounds); - } - - @Override - public void dismissAllApps() { - if (!DISABLE_ALL_APPS_SEARCH_INTEGRATION) { - // Dismiss All Apps if we aren't already paused/invisible - if (!mPaused) { - showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true, - null /* onCompleteRunnable */, false /* notifyLauncherCallbacks */); - } - } - } - - @Override - public void setSearchManager(AppSearchManager manager) { - mAppsView.setSearchManager(manager); - } - }); mLauncherCallbacks.setLauncherSearchCallback(new Launcher.LauncherSearchCallbacks() { private boolean mImportanceStored = false; private int mWorkspaceImportanceForAccessibility = @@ -638,6 +609,14 @@ public class Launcher extends Activity } } + /** + * Updates the bounds of all the overlays to match the new fixed bounds. + */ + public void updateOverlayBounds(Rect newBounds) { + mAppsView.setSearchBarBounds(newBounds); + mWidgetsView.setSearchBarBounds(newBounds); + } + /** To be overridden by subclasses to hint to Launcher that we have custom content */ protected boolean hasCustomContentToLeft() { if (mLauncherCallbacks != null) { @@ -1012,16 +991,6 @@ public class Launcher extends Activity } mOnResumeState = State.NONE; - // Restore the apps state if we are in all apps - if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) { - // Otherwise, notify the callbacks if we are in all apps mode - if (mState == State.APPS) { - if (mLauncherCallbacks != null) { - mLauncherCallbacks.onAllAppsShown(); - } - } - } - // Background was set to gradient in onPause(), restore to transparent if in all apps. setWorkspaceBackground(mState == State.WORKSPACE ? WORKSPACE_BACKGROUND_GRADIENT : WORKSPACE_BACKGROUND_TRANSPARENT); @@ -1167,17 +1136,20 @@ public class Launcher extends Activity * Updates launcher to the available space that AllApps can take so as not to overlap with * any other views. */ + @Deprecated public void onAllAppsBoundsChanged(Rect bounds); /** * Called to dismiss all apps if it is showing. */ + @Deprecated public void dismissAllApps(); /** * Sets the search manager to be used for app search. */ - public void setSearchManager(AppSearchManager manager); + @Deprecated + public void setSearchManager(Object manager); } public interface LauncherSearchCallbacks { @@ -1463,14 +1435,14 @@ public class Launcher extends Activity mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.search_drop_target_bar); - // Setup Apps + // Setup Apps and Widgets mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view); - if (isAllAppsSearchOverridden()) { - mAppsView.hideHeaderBar(); - } - - // Setup AppsCustomize mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view); + if (mLauncherCallbacks != null && mLauncherCallbacks.getAllAppsSearchBarController() != null) { + mAppsView.setSearchBarController(mLauncherCallbacks.getAllAppsSearchBarController()); + } else { + mAppsView.setSearchBarController(mAppsView.newDefaultAppSearchController()); + } // Setup the drag controller (drop targets have to be added in reverse order in priority) dragController.setDragScoller(mWorkspace); @@ -2866,17 +2838,8 @@ public class Launcher extends Activity public void updateInteraction(Workspace.State fromState, Workspace.State toState) { // Only update the interacting state if we are transitioning to/from a view with an // overlay - boolean fromStateWithOverlay; - boolean toStateWithOverlay; - if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) { - fromStateWithOverlay = fromState != Workspace.State.NORMAL; - toStateWithOverlay = toState != Workspace.State.NORMAL; - } else { - fromStateWithOverlay = fromState != Workspace.State.NORMAL && - fromState != Workspace.State.NORMAL_HIDDEN; - toStateWithOverlay = toState != Workspace.State.NORMAL && - toState != Workspace.State.NORMAL_HIDDEN; - } + boolean fromStateWithOverlay = fromState != Workspace.State.NORMAL; + boolean toStateWithOverlay = toState != Workspace.State.NORMAL; if (toStateWithOverlay) { onInteractionBegin(); } else if (fromStateWithOverlay) { @@ -3320,21 +3283,19 @@ public class Launcher extends Activity } public void showWorkspace(boolean animated) { - showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null, - true); + showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, null); } public void showWorkspace(boolean animated, Runnable onCompleteRunnable) { showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, animated, - onCompleteRunnable, true); + onCompleteRunnable); } protected void showWorkspace(int snapToPage, boolean animated) { - showWorkspace(snapToPage, animated, null, true); + showWorkspace(snapToPage, animated, null); } - void showWorkspace(int snapToPage, boolean animated, Runnable onCompleteRunnable, - boolean notifyLauncherCallbacks) { + void showWorkspace(int snapToPage, boolean animated, Runnable onCompleteRunnable) { boolean changed = mState != State.WORKSPACE || mWorkspace.getState() != Workspace.State.NORMAL; if (changed) { @@ -3366,12 +3327,6 @@ public class Launcher extends Activity // Send an accessibility event to announce the context change getWindow().getDecorView() .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - if (notifyLauncherCallbacks) { - // Dismiss all apps when the workspace is shown - if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) { - mLauncherCallbacks.onAllAppsHidden(); - } - } } } @@ -3431,10 +3386,7 @@ public class Launcher extends Activity } if (toState == State.APPS) { - mStateTransitionAnimation.startAnimationToAllApps(animated); - if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) { - mLauncherCallbacks.onAllAppsShown(); - } + mStateTransitionAnimation.startAnimationToAllApps(mState, animated); } else { mStateTransitionAnimation.startAnimationToWidgets(animated); } @@ -3458,9 +3410,10 @@ public class Launcher extends Activity * new state. */ public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, int toPage, - boolean animated, HashMap layerViews) { + boolean animated, boolean hasOverlaySearchBar, HashMap layerViews) { Workspace.State fromState = mWorkspace.getState(); - Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated, layerViews); + Animator anim = mWorkspace.setStateWithAnimation(toState, toPage, animated, + hasOverlaySearchBar, layerViews); updateInteraction(fromState, toState); return anim; } @@ -3482,14 +3435,6 @@ public class Launcher extends Activity final Runnable onCompleteRunnable) { if (mState != State.APPS_SPRING_LOADED && mState != State.WIDGETS_SPRING_LOADED) return; - if (successfulDrop) { - // We need to trigger all apps hidden to notify search to update itself before the - // delayed call to showWorkspace below - if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) { - mLauncherCallbacks.onAllAppsHidden(); - } - } - mHandler.postDelayed(new Runnable() { @Override public void run() { @@ -4473,20 +4418,6 @@ public class Launcher extends Activity return null; } - /** - * Returns whether the launcher callbacks overrides search in all apps. - */ - @Thunk boolean isAllAppsSearchOverridden() { - if (DISABLE_ALL_APPS_SEARCH_INTEGRATION) { - return false; - } - - if (mLauncherCallbacks != null) { - return mLauncherCallbacks.overrideAllAppsSearch(); - } - return false; - } - private boolean shouldRunFirstRunActivity() { return !ActivityManager.isRunningInTestHarness() && !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false); diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java index 70e400bca..e73275400 100644 --- a/src/com/android/launcher3/LauncherCallbacks.java +++ b/src/com/android/launcher3/LauncherCallbacks.java @@ -7,6 +7,7 @@ import android.os.Bundle; import android.view.Menu; import android.view.View; import android.view.ViewGroup; +import com.android.launcher3.allapps.AllAppsSearchBarController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -51,12 +52,9 @@ public interface LauncherCallbacks { public void onLauncherProviderChange(); public void finishBindingItems(final boolean upgradePath); public void onClickAllAppsButton(View v); - public void onAllAppsShown(); - public void onAllAppsHidden(); public void bindAllApplications(ArrayList apps); public void onClickFolderIcon(View v); public void onClickAppShortcut(View v); - @Deprecated public void onClickPagedViewIcon(View v); public void onClickWallpaperPicker(View v); @@ -89,10 +87,11 @@ public interface LauncherCallbacks { public View getIntroScreen(); public boolean shouldMoveToDefaultScreenOnHomeIntent(); public boolean hasSettings(); + @Deprecated public ComponentName getWallpaperPickerComponent(); public boolean overrideWallpaperDimensions(); public boolean isLauncherPreinstalled(); - public boolean overrideAllAppsSearch(); + public AllAppsSearchBarController getAllAppsSearchBarController(); public List getPredictedApps(); /** @@ -113,14 +112,6 @@ public interface LauncherCallbacks { public Launcher.LauncherOverlay setLauncherOverlayView(InsettableFrameLayout container, Launcher.LauncherOverlayCallbacks callbacks); - /** - * Sets the callbacks to allow any extensions to callback to the launcher. - * - * @param callbacks A set of callbacks to the Launcher, is actually a LauncherAppsCallback, but - * for implementation purposes is passed around as an object. - */ - public void setLauncherAppsCallback(Object callbacks); - /** * Sets the callbacks to allow reacting the actions of search overlays of the launcher. * diff --git a/src/com/android/launcher3/LauncherExtension.java b/src/com/android/launcher3/LauncherExtension.java index 09a105bcc..fafb070ec 100644 --- a/src/com/android/launcher3/LauncherExtension.java +++ b/src/com/android/launcher3/LauncherExtension.java @@ -11,6 +11,7 @@ import android.os.Bundle; import android.view.Menu; import android.view.View; import android.view.ViewGroup; +import com.android.launcher3.allapps.AllAppsSearchBarController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -124,14 +125,6 @@ public class LauncherExtension extends Launcher { public void onClickAllAppsButton(View v) { } - @Override - public void onAllAppsShown() { - } - - @Override - public void onAllAppsHidden() { - } - @Override public void bindAllApplications(ArrayList apps) { } @@ -255,8 +248,8 @@ public class LauncherExtension extends Launcher { } @Override - public boolean overrideAllAppsSearch() { - return false; + public AllAppsSearchBarController getAllAppsSearchBarController() { + return null; } @Override @@ -284,11 +277,6 @@ public class LauncherExtension extends Launcher { return mLauncherOverlay; } - @Override - public void setLauncherAppsCallback(Object callbacks) { - // Do nothing - } - @Override public void setLauncherSearchCallback(Object callbacks) { // Do nothing diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java index a006d141b..e94a2ac0b 100644 --- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java +++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java @@ -129,7 +129,7 @@ public class LauncherStateTransitionAnimation { /** * Starts an animation to the apps view. */ - public void startAnimationToAllApps(final boolean animated) { + public void startAnimationToAllApps(final Launcher.State fromState, final boolean animated) { final AllAppsContainerView toView = mLauncher.getAppsView(); PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() { private int[] mAllAppsToPanelDelta; @@ -137,7 +137,6 @@ public class LauncherStateTransitionAnimation { @Override public void onRevealViewVisible(View revealView, View contentView, View allAppsButtonView) { - toView.setBackground(null); // Get the y delta between the center of the page and the center of the all apps // button mAllAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView, @@ -173,9 +172,9 @@ public class LauncherStateTransitionAnimation { }; } }; + // Only animate the search bar if animating from spring loaded mode back to all apps startAnimationToOverlay(Workspace.State.NORMAL_HIDDEN, toView, toView.getContentView(), - toView.getRevealView(), animated, - !mLauncher.isAllAppsSearchOverridden() /* hideSearchBar */, cb); + toView.getRevealView(), toView.getSearchBarView(), animated, true, cb); } /** @@ -188,7 +187,6 @@ public class LauncherStateTransitionAnimation { @Override public void onRevealViewVisible(View revealView, View contentView, View allAppsButtonView) { - revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark)); } @Override public float getMaterialRevealViewFinalAlpha(View revealView) { @@ -200,8 +198,8 @@ public class LauncherStateTransitionAnimation { } }; startAnimationToOverlay(Workspace.State.OVERVIEW_HIDDEN, toView, - toView.getContentView(), toView.getRevealView(), animated, true /* hideSearchBar */, - cb); + toView.getContentView(), toView.getRevealView(), null, animated, + true /* hideSearchBar */, cb); } /** @@ -217,10 +215,10 @@ public class LauncherStateTransitionAnimation { } if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) { - startAnimationToWorkspaceFromAllApps(fromState, toWorkspaceState, toWorkspacePage, + startAnimationToWorkspaceFromAllApps(toWorkspaceState, toWorkspacePage, animated, onCompleteRunnable); } else { - startAnimationToWorkspaceFromWidgets(fromState, toWorkspaceState, toWorkspacePage, + startAnimationToWorkspaceFromWidgets(toWorkspaceState, toWorkspacePage, animated, onCompleteRunnable); } } @@ -230,8 +228,9 @@ public class LauncherStateTransitionAnimation { */ @SuppressLint("NewApi") private void startAnimationToOverlay(final Workspace.State toWorkspaceState, final View toView, - final View contentView, final View revealView, final boolean animated, - final boolean hideSearchBar, final PrivateTransitionCallbacks pCb) { + final View contentView, final View revealView, final View overlaySearchBarView, + final boolean animated, final boolean hideSearchBar, + final PrivateTransitionCallbacks pCb) { final Resources res = mLauncher.getResources(); final boolean material = Utilities.isLmpOrAbove(); final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); @@ -252,7 +251,7 @@ public class LauncherStateTransitionAnimation { // Create the workspace animation. // NOTE: this call apparently also sets the state for the workspace if !animated Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, -1, - animated, layerViews); + animated, overlaySearchBarView != null /* hasOverlaySearchBar */, layerViews); if (animated && initialized) { mStateAnimation = LauncherAnimUtils.createAnimatorSet(); @@ -297,6 +296,15 @@ public class LauncherStateTransitionAnimation { layerViews.put(revealView, BUILD_AND_SET_LAYER); mStateAnimation.play(panelAlphaAndDrift); + if (overlaySearchBarView != null) { + overlaySearchBarView.setAlpha(0f); + ObjectAnimator searchBarAlpha = ObjectAnimator.ofFloat(overlaySearchBarView, "alpha", 0f, 1f); + searchBarAlpha.setDuration(100); + searchBarAlpha.setInterpolator(new AccelerateInterpolator(1.5f)); + layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER); + mStateAnimation.play(searchBarAlpha); + } + // Setup the animation for the content view contentView.setVisibility(View.VISIBLE); contentView.setAlpha(0f); @@ -426,9 +434,8 @@ public class LauncherStateTransitionAnimation { /** * Starts and animation to the workspace from the apps view. */ - private void startAnimationToWorkspaceFromAllApps(final Launcher.State fromState, - final Workspace.State toWorkspaceState, final int toWorkspacePage, - final boolean animated, final Runnable onCompleteRunnable) { + private void startAnimationToWorkspaceFromAllApps(final Workspace.State toWorkspaceState, + final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) { AllAppsContainerView appsView = mLauncher.getAppsView(); PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() { int[] mAllAppsToPanelDelta; @@ -479,24 +486,23 @@ public class LauncherStateTransitionAnimation { }; } }; + // Only animate the search bar if animating to spring loaded mode from all apps startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage, appsView, - appsView.getContentView(), appsView.getRevealView(), animated, onCompleteRunnable, - cb); + appsView.getContentView(), appsView.getRevealView(), appsView.getSearchBarView(), + animated, onCompleteRunnable, cb); } /** * Starts and animation to the workspace from the widgets view. */ - private void startAnimationToWorkspaceFromWidgets(final Launcher.State fromState, - final Workspace.State toWorkspaceState, final int toWorkspacePage, - final boolean animated, final Runnable onCompleteRunnable) { + private void startAnimationToWorkspaceFromWidgets(final Workspace.State toWorkspaceState, + final int toWorkspacePage, final boolean animated, final Runnable onCompleteRunnable) { final WidgetsContainerView widgetsView = mLauncher.getWidgetsView(); final Resources res = mLauncher.getResources(); PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() { @Override public void onRevealViewVisible(View revealView, View contentView, View allAppsButtonView) { - revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark)); } @Override public float getMaterialRevealViewFinalYDrift(View revealView) { @@ -518,7 +524,7 @@ public class LauncherStateTransitionAnimation { } }; startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage, widgetsView, - widgetsView.getContentView(), widgetsView.getRevealView(), animated, + widgetsView.getContentView(), widgetsView.getRevealView(), null, animated, onCompleteRunnable, cb); } @@ -527,8 +533,8 @@ public class LauncherStateTransitionAnimation { */ private void startAnimationToWorkspaceFromOverlay(final Workspace.State toWorkspaceState, final int toWorkspacePage, final View fromView, final View contentView, - final View revealView, final boolean animated, final Runnable onCompleteRunnable, - final PrivateTransitionCallbacks pCb) { + final View revealView, final View overlaySearchBarView, final boolean animated, + final Runnable onCompleteRunnable, final PrivateTransitionCallbacks pCb) { final Resources res = mLauncher.getResources(); final boolean material = Utilities.isLmpOrAbove(); final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime); @@ -549,7 +555,8 @@ public class LauncherStateTransitionAnimation { // Create the workspace animation. // NOTE: this call apparently also sets the state for the workspace if !animated Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, - toWorkspacePage, animated, layerViews); + toWorkspacePage, animated, overlaySearchBarView != null /* hasOverlaySearchBar */, + layerViews); if (animated && initialized) { mStateAnimation = LauncherAnimUtils.createAnimatorSet(); @@ -633,6 +640,16 @@ public class LauncherStateTransitionAnimation { itemsAlpha.setInterpolator(decelerateInterpolator); mStateAnimation.play(itemsAlpha); + if (overlaySearchBarView != null) { + overlaySearchBarView.setAlpha(1f); + ObjectAnimator searchAlpha = ObjectAnimator.ofFloat(overlaySearchBarView, "alpha", 1f, 0f); + searchAlpha.setDuration(material ? 100 : 150); + searchAlpha.setInterpolator(decelerateInterpolator); + searchAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY); + layerViews.put(overlaySearchBarView, BUILD_AND_SET_LAYER); + mStateAnimation.play(searchAlpha); + } + if (material) { // Animate the all apps button float finalRadius = pCb.getMaterialRevealViewStartFinalRadius(); @@ -681,6 +698,9 @@ public class LauncherStateTransitionAnimation { contentView.setTranslationY(0); contentView.setAlpha(1); } + if (overlaySearchBarView != null) { + overlaySearchBarView.setAlpha(1f); + } // This can hold unnecessary references to views. mStateAnimation = null; diff --git a/src/com/android/launcher3/LauncherTransitionable.java b/src/com/android/launcher3/LauncherTransitionable.java index 9442abcde..49af6928a 100644 --- a/src/com/android/launcher3/LauncherTransitionable.java +++ b/src/com/android/launcher3/LauncherTransitionable.java @@ -16,13 +16,10 @@ package com.android.launcher3; -import android.view.View; - /** * An interface to get callbacks during a launcher transition. */ public interface LauncherTransitionable { - View getContent(); void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace); void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace); void onLauncherTransitionStep(Launcher l, float t); diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 19195b421..76f872bae 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -1998,10 +1998,10 @@ public class Workspace extends PagedView * to that new state. */ public Animator setStateWithAnimation(State toState, int toPage, boolean animated, - HashMap layerViews) { + boolean hasOverlaySearchBar, HashMap layerViews) { // Create the animation to the new state Animator workspaceAnim = mStateTransitionAnimation.getAnimationToState(getState(), - toState, toPage, animated, layerViews); + toState, toPage, animated, hasOverlaySearchBar, layerViews); // Update the current state mState = toState; @@ -2100,11 +2100,6 @@ public class Workspace extends PagedView } } - @Override - public View getContent() { - return this; - } - /** * Returns the drawable for the given text view. */ diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java index e360e889b..13e4a59f1 100644 --- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java +++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java @@ -121,6 +121,52 @@ class ZoomInInterpolator implements TimeInterpolator { } } +/** + * Stores the transition states for convenience. + */ +class TransitionStates { + + // Raw states + final boolean oldStateIsNormal; + final boolean oldStateIsSpringLoaded; + final boolean oldStateIsNormalHidden; + final boolean oldStateIsOverviewHidden; + final boolean oldStateIsOverview; + + final boolean stateIsNormal; + final boolean stateIsSpringLoaded; + final boolean stateIsNormalHidden; + final boolean stateIsOverviewHidden; + final boolean stateIsOverview; + + // Convenience members + final boolean workspaceToAllApps; + final boolean overviewToAllApps; + final boolean allAppsToWorkspace; + final boolean workspaceToOverview; + final boolean overviewToWorkspace; + + public TransitionStates(final Workspace.State fromState, final Workspace.State toState) { + oldStateIsNormal = (fromState == Workspace.State.NORMAL); + oldStateIsSpringLoaded = (fromState == Workspace.State.SPRING_LOADED); + oldStateIsNormalHidden = (fromState == Workspace.State.NORMAL_HIDDEN); + oldStateIsOverviewHidden = (fromState == Workspace.State.OVERVIEW_HIDDEN); + oldStateIsOverview = (fromState == Workspace.State.OVERVIEW); + + stateIsNormal = (toState == Workspace.State.NORMAL); + stateIsSpringLoaded = (toState == Workspace.State.SPRING_LOADED); + stateIsNormalHidden = (toState == Workspace.State.NORMAL_HIDDEN); + stateIsOverviewHidden = (toState == Workspace.State.OVERVIEW_HIDDEN); + stateIsOverview = (toState == Workspace.State.OVERVIEW); + + workspaceToOverview = (oldStateIsNormal && stateIsOverview); + workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden); + overviewToWorkspace = (oldStateIsOverview && stateIsNormal); + overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden); + allAppsToWorkspace = (stateIsNormalHidden && stateIsNormal); + } +} + /** * Manages the animations between each of the workspace states. */ @@ -175,9 +221,17 @@ public class WorkspaceStateTransitionAnimation { } public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState, - int toPage, boolean animated, - HashMap layerViews) { - getAnimation(fromState, toState, toPage, animated, layerViews); + int toPage, boolean animated, boolean hasOverlaySearchBar, + HashMap layerViews) { + AccessibilityManager am = (AccessibilityManager) + mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE); + final boolean accessibilityEnabled = am.isEnabled(); + TransitionStates states = new TransitionStates(fromState, toState); + int duration = getAnimationDuration(states); + animateWorkspace(states, toPage, animated, duration, layerViews, + accessibilityEnabled); + animateSearchBar(states, animated, duration, hasOverlaySearchBar, layerViews, + accessibilityEnabled); return mStateAnimator; } @@ -186,15 +240,37 @@ public class WorkspaceStateTransitionAnimation { } /** - * Starts a transition animation for the workspace. + * Reinitializes the arrays that we need for the animations on each page. */ - private void getAnimation(final Workspace.State fromState, final Workspace.State toState, - int toPage, final boolean animated, - final HashMap layerViews) { - AccessibilityManager am = (AccessibilityManager) - mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE); - final boolean accessibilityEnabled = am.isEnabled(); + private void reinitializeAnimationArrays() { + final int childCount = mWorkspace.getChildCount(); + if (mLastChildCount == childCount) return; + mOldBackgroundAlphas = new float[childCount]; + mOldAlphas = new float[childCount]; + mNewBackgroundAlphas = new float[childCount]; + mNewAlphas = new float[childCount]; + } + + /** + * Returns the proper animation duration for a transition. + */ + private int getAnimationDuration(TransitionStates states) { + if (states.workspaceToAllApps || states.overviewToAllApps) { + return mAllAppsTransitionTime; + } else if (states.workspaceToOverview || states.overviewToWorkspace) { + return mOverviewTransitionTime; + } else { + return mOverlayTransitionTime; + } + } + + /** + * Starts a transition animation for the workspace. + */ + private void animateWorkspace(final TransitionStates states, int toPage, final boolean animated, + final int duration, final HashMap layerViews, + final boolean accessibilityEnabled) { // Reinitialize animation arrays for the current workspace state reinitializeAnimationArrays(); @@ -205,32 +281,12 @@ public class WorkspaceStateTransitionAnimation { } // Update the workspace state - final boolean oldStateIsNormal = (fromState == Workspace.State.NORMAL); - final boolean oldStateIsSpringLoaded = (fromState == Workspace.State.SPRING_LOADED); - final boolean oldStateIsNormalHidden = (fromState == Workspace.State.NORMAL_HIDDEN); - final boolean oldStateIsOverviewHidden = (fromState == Workspace.State.OVERVIEW_HIDDEN); - final boolean oldStateIsOverview = (fromState == Workspace.State.OVERVIEW); - - final boolean stateIsNormal = (toState == Workspace.State.NORMAL); - final boolean stateIsSpringLoaded = (toState == Workspace.State.SPRING_LOADED); - final boolean stateIsNormalHidden = (toState == Workspace.State.NORMAL_HIDDEN); - final boolean stateIsOverviewHidden = (toState == Workspace.State.OVERVIEW_HIDDEN); - final boolean stateIsOverview = (toState == Workspace.State.OVERVIEW); - - final boolean workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden); - final boolean overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden); - final boolean allAppsToWorkspace = (stateIsNormalHidden && stateIsNormal); - final boolean workspaceToOverview = (oldStateIsNormal && stateIsOverview); - final boolean overviewToWorkspace = (oldStateIsOverview && stateIsNormal); - - float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f; - float finalHotseatAndPageIndicatorAlpha = (stateIsNormal || stateIsSpringLoaded) ? 1f : 0f; - float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f; - // We keep the search bar visible on the workspace and in AllApps now - boolean showSearchBar = stateIsNormal || - (mLauncher.isAllAppsSearchOverridden() && stateIsNormalHidden); - float finalSearchBarAlpha = showSearchBar ? 1f : 0f; - float finalWorkspaceTranslationY = stateIsOverview || stateIsOverviewHidden ? + float finalBackgroundAlpha = (states.stateIsSpringLoaded || states.stateIsOverview) ? + 1.0f : 0f; + float finalHotseatAndPageIndicatorAlpha = (states.stateIsNormal || states.stateIsSpringLoaded) ? + 1f : 0f; + float finalOverviewPanelAlpha = states.stateIsOverview ? 1f : 0f; + float finalWorkspaceTranslationY = states.stateIsOverview || states.stateIsOverviewHidden ? mWorkspace.getOverviewModeTranslationY() : 0; final int childCount = mWorkspace.getChildCount(); @@ -238,29 +294,20 @@ public class WorkspaceStateTransitionAnimation { mNewScale = 1.0f; - if (oldStateIsOverview) { + if (states.oldStateIsOverview) { mWorkspace.disableFreeScroll(); - } else if (stateIsOverview) { + } else if (states.stateIsOverview) { mWorkspace.enableFreeScroll(); } - if (!stateIsNormal) { - if (stateIsSpringLoaded) { + if (!states.stateIsNormal) { + if (states.stateIsSpringLoaded) { mNewScale = mSpringLoadedShrinkFactor; - } else if (stateIsOverview || stateIsOverviewHidden) { + } else if (states.stateIsOverview || states.stateIsOverviewHidden) { mNewScale = mOverviewModeShrinkFactor; } } - final int duration; - if (workspaceToAllApps || overviewToAllApps) { - duration = mAllAppsTransitionTime; - } else if (workspaceToOverview || overviewToWorkspace) { - duration = mOverviewTransitionTime; - } else { - duration = mOverlayTransitionTime; - } - if (toPage == SCROLL_TO_CURRENT_PAGE) { toPage = mWorkspace.getPageNearestToCenterOfScreen(); } @@ -271,9 +318,9 @@ public class WorkspaceStateTransitionAnimation { boolean isCurrentPage = (i == toPage); float initialAlpha = cl.getShortcutsAndWidgets().getAlpha(); float finalAlpha; - if (stateIsNormalHidden || stateIsOverviewHidden) { + if (states.stateIsNormalHidden || states.stateIsOverviewHidden) { finalAlpha = 0f; - } else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) { + } else if (states.stateIsNormal && mWorkspaceFadeInAdjacentScreens) { finalAlpha = (i == toPage || i < customPageCount) ? 1f : 0f; } else { finalAlpha = 1f; @@ -282,8 +329,8 @@ public class WorkspaceStateTransitionAnimation { // If we are animating to/from the small state, then hide the side pages and fade the // current page in if (!mWorkspace.isSwitchingState()) { - if (workspaceToAllApps || allAppsToWorkspace) { - if (allAppsToWorkspace && isCurrentPage) { + if (states.workspaceToAllApps || states.allAppsToWorkspace) { + if (states.allAppsToWorkspace && isCurrentPage) { initialAlpha = 0f; } else if (!isCurrentPage) { initialAlpha = finalAlpha = 0f; @@ -303,7 +350,6 @@ public class WorkspaceStateTransitionAnimation { } } - final View searchBar = mLauncher.getOrCreateQsbBar(); final ViewGroup overviewPanel = mLauncher.getOverviewPanel(); final View hotseat = mLauncher.getHotseat(); final View pageIndicator = mWorkspace.getPageIndicator(); @@ -345,7 +391,7 @@ public class WorkspaceStateTransitionAnimation { } } } - Animator pageIndicatorAlpha = null; + Animator pageIndicatorAlpha; if (pageIndicator != null) { pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator) .alpha(finalHotseatAndPageIndicatorAlpha).withLayer(); @@ -380,11 +426,11 @@ public class WorkspaceStateTransitionAnimation { overviewPanelAlpha.withLayer(); } - if (workspaceToOverview) { + if (states.workspaceToOverview) { pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2)); hotseatAlpha.setInterpolator(new DecelerateInterpolator(2)); overviewPanelAlpha.setInterpolator(null); - } else if (overviewToWorkspace) { + } else if (states.overviewToWorkspace) { pageIndicatorAlpha.setInterpolator(null); hotseatAlpha.setInterpolator(null); overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2)); @@ -394,26 +440,6 @@ public class WorkspaceStateTransitionAnimation { pageIndicatorAlpha.setDuration(duration); hotseatAlpha.setDuration(duration); - // TODO: This should really be coordinated with the SearchDropTargetBar, otherwise the - // bar has no idea that it is hidden, and this has no idea what state the bar is - // actually in. - if (searchBar != null) { - LauncherViewPropertyAnimator searchBarAlpha = new LauncherViewPropertyAnimator(searchBar) - .alpha(finalSearchBarAlpha); - searchBarAlpha.addListener(new AlphaUpdateListener(searchBar, accessibilityEnabled)); - searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null); - if (layerViews != null) { - // If layerViews is not null, we add these views, and indicate that - // the caller can manage layer state. - layerViews.put(searchBar, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER); - } else { - // Otherwise let the animator handle layer management. - searchBarAlpha.withLayer(); - } - searchBarAlpha.setDuration(duration); - mStateAnimator.play(searchBarAlpha); - } - mStateAnimator.play(overviewPanelAlpha); mStateAnimator.play(hotseatAlpha); mStateAnimator.play(pageIndicatorAlpha); @@ -437,10 +463,6 @@ public class WorkspaceStateTransitionAnimation { pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha); AlphaUpdateListener.updateVisibility(pageIndicator, accessibilityEnabled); } - if (searchBar != null) { - searchBar.setAlpha(finalSearchBarAlpha); - AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled); - } mWorkspace.updateCustomContentVisibility(); mWorkspace.setScaleX(mNewScale); mWorkspace.setScaleY(mNewScale); @@ -452,7 +474,7 @@ public class WorkspaceStateTransitionAnimation { } } - if (stateIsNormal) { + if (states.stateIsNormal) { animateBackgroundGradient(0f, animated); } else { animateBackgroundGradient(mWorkspaceScrimAlpha, animated); @@ -460,16 +482,69 @@ public class WorkspaceStateTransitionAnimation { } /** - * Reinitializes the arrays that we need for the animations on each page. + * Coordinates with the workspace animation to animate the search bar. + * + * TODO: This should really be coordinated with the SearchDropTargetBar, otherwise the + * bar has no idea that it is hidden, and this has no idea what state the bar is + * actually in. */ - private void reinitializeAnimationArrays() { - final int childCount = mWorkspace.getChildCount(); - if (mLastChildCount == childCount) return; + private void animateSearchBar(TransitionStates states, boolean animated, int duration, + boolean hasOverlaySearchBar, final HashMap layerViews, + final boolean accessibilityEnabled) { - mOldBackgroundAlphas = new float[childCount]; - mOldAlphas = new float[childCount]; - mNewBackgroundAlphas = new float[childCount]; - mNewAlphas = new float[childCount]; + // The search bar is only visible in the workspace + final View searchBar = mLauncher.getOrCreateQsbBar(); + if (searchBar != null) { + final boolean searchBarWillBeShown = states.stateIsNormal; + final float finalSearchBarAlpha = searchBarWillBeShown ? 1f : 0f; + if (animated) { + if (hasOverlaySearchBar) { + // If there is an overlay search bar, then we will coordinate with it. + mStateAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + // If we are transitioning to a visible search bar, show it immediately + // and let the overlay search bar has faded out + if (searchBarWillBeShown) { + searchBar.setAlpha(finalSearchBarAlpha); + AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + // If we are transitioning to a hidden search bar, hide it only after + // the overlay search bar has faded in + if (!searchBarWillBeShown) { + searchBar.setAlpha(finalSearchBarAlpha); + AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled); + } + } + }); + } else { + // Otherwise, we can just do the normal animation + LauncherViewPropertyAnimator searchBarAlpha = + new LauncherViewPropertyAnimator(searchBar).alpha(finalSearchBarAlpha); + searchBarAlpha.addListener(new AlphaUpdateListener(searchBar, + accessibilityEnabled)); + searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null); + if (layerViews != null) { + // If layerViews is not null, we add these views, and indicate that + // the caller can manage layer state. + layerViews.put(searchBar, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER); + } else { + // Otherwise let the animator handle layer management. + searchBarAlpha.withLayer(); + } + searchBarAlpha.setDuration(duration); + mStateAnimator.play(searchBarAlpha); + } + } else { + // Set the search bar state immediately + searchBar.setAlpha(finalSearchBarAlpha); + AlphaUpdateListener.updateVisibility(searchBar, accessibilityEnabled); + } + } } /** diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 855a4430d..b300cae62 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -20,17 +20,15 @@ import android.annotation.TargetApi; import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; -import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; import android.os.Bundle; import android.support.v7.widget.RecyclerView; -import android.text.Editable; -import android.text.TextWatcher; +import android.text.Selection; +import android.text.SpannableStringBuilder; +import android.text.method.TextKeyListener; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -39,11 +37,8 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewTreeObserver; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; -import android.widget.TextView; - +import android.widget.LinearLayout; import com.android.launcher3.AppInfo; import com.android.launcher3.BaseContainerView; import com.android.launcher3.BubbleTextView; @@ -54,7 +49,6 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; import com.android.launcher3.Folder; -import com.android.launcher3.Insettable; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherTransitionable; @@ -62,131 +56,24 @@ import com.android.launcher3.R; import com.android.launcher3.Stats; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; -import com.android.launcher3.allapps.AppSearchManager.AppSearchResultCallback; import com.android.launcher3.util.Thunk; import java.util.ArrayList; import java.util.List; -/** - * Interface for controlling the header elevation in response to RecyclerView scroll. - */ -interface HeaderElevationController { - void onScroll(int scrollY); - void updateBackgroundPadding(Drawable bg); - void disable(); -} - -/** - * Implementation of the header elevation mechanism for pre-L devices. It simulates elevation - * by drawing a gradient under the header bar. - */ -final class HeaderElevationControllerV16 implements HeaderElevationController { - - private final View mShadow; - private final float mScrollToElevation; - private final Rect mTmpRect = new Rect(); - - public HeaderElevationControllerV16(View header) { - Resources res = header.getContext().getResources(); - mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation); - - mShadow = new View(header.getContext()); - mShadow.setBackground(new GradientDrawable( - GradientDrawable.Orientation.TOP_BOTTOM, new int[] {0x44000000, 0x00000000})); - mShadow.setAlpha(0); - - FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - res.getDimensionPixelSize(R.dimen.all_apps_header_shadow_height)); - lp.topMargin = ((FrameLayout.LayoutParams) header.getLayoutParams()).height; - - ((ViewGroup) header.getParent()).addView(mShadow, lp); - } - - @Override - public void onScroll(int scrollY) { - float elevationPct = (float) Math.min(scrollY, mScrollToElevation) / - mScrollToElevation; - mShadow.setAlpha(elevationPct); - } - - @Override - public void updateBackgroundPadding(Drawable bg) { - bg.getPadding(mTmpRect); - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mShadow.getLayoutParams(); - lp.leftMargin = mTmpRect.left; - lp.rightMargin = mTmpRect.right; - mShadow.requestLayout(); - } - - @Override - public void disable() { - ViewGroup parent = (ViewGroup) mShadow.getParent(); - if (parent != null) { - parent.removeView(mShadow); - } - } -} - -/** - * Implementation of the header elevation mechanism for L+ devices, which makes use of the native - * view elevation. - */ -@TargetApi(Build.VERSION_CODES.LOLLIPOP) -final class HeaderElevationControllerVL implements HeaderElevationController { - - private final View mHeader; - private final float mMaxElevation; - private final float mScrollToElevation; - - public HeaderElevationControllerVL(View header) { - mHeader = header; - - Resources res = header.getContext().getResources(); - mMaxElevation = res.getDimension(R.dimen.all_apps_header_max_elevation); - mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation); - } - - @Override - public void onScroll(int scrollY) { - float elevationPct = (float) Math.min(scrollY, mScrollToElevation) / - mScrollToElevation; - float newElevation = mMaxElevation * elevationPct; - if (Float.compare(mHeader.getElevation(), newElevation) != 0) { - mHeader.setElevation(newElevation); - } - } - - @Override - public void updateBackgroundPadding(Drawable bg) { - // Do nothing, the background padding on the header view is already applied - } - - @Override - public void disable() { } -} /** * The all apps view container. */ -public class AllAppsContainerView extends BaseContainerView implements DragSource, Insettable, - TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable, - AlphabeticalAppsList.AdapterChangedCallback, AllAppsGridAdapter.PredictionBarSpacerCallbacks, - View.OnTouchListener, View.OnClickListener, View.OnLongClickListener, - ViewTreeObserver.OnPreDrawListener, AppSearchResultCallback, Stats.LaunchSourceProvider { +public class AllAppsContainerView extends BaseContainerView implements DragSource, + LauncherTransitionable, AlphabeticalAppsList.AdapterChangedCallback, + AllAppsGridAdapter.PredictionBarSpacerCallbacks, View.OnTouchListener, + View.OnLongClickListener, ViewTreeObserver.OnPreDrawListener, + AllAppsSearchBarController.Callbacks, Stats.LaunchSourceProvider { public static final boolean GRID_MERGE_SECTIONS = true; - private static final boolean ALLOW_SINGLE_APP_LAUNCH = true; - private static final boolean DYNAMIC_HEADER_ELEVATION = true; - private static final boolean DISMISS_SEARCH_ON_BACK = true; - - private static final int FADE_IN_DURATION = 175; - private static final int FADE_OUT_DURATION = 100; - private static final int SEARCH_TRANSLATION_X_DP = 18; - @Thunk Launcher mLauncher; @Thunk AlphabeticalAppsList mApps; private LayoutInflater mLayoutInflater; @@ -194,16 +81,14 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc private RecyclerView.LayoutManager mLayoutManager; private RecyclerView.ItemDecoration mItemDecoration; - @Thunk FrameLayout mContentView; + @Thunk View mContent; + @Thunk View mContainerView; + @Thunk View mRevealView; @Thunk AllAppsRecyclerView mAppsRecyclerView; @Thunk ViewGroup mPredictionBarView; - private View mHeaderView; - @Thunk View mSearchBarContainerView; - private View mSearchButtonView; - private View mDismissSearchButtonView; - @Thunk AllAppsSearchEditView mSearchBarEditView; - - private HeaderElevationController mElevationController; + @Thunk AllAppsSearchBarController mSearchBarController; + private ViewGroup mSearchBarContainerView; + private View mSearchBarView; private int mNumAppsPerRow; private int mNumPredictedAppsPerRow; @@ -213,18 +98,16 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc private final Point mIconLastTouchPos = new Point(); // This coordinate is used to proxy click and long-click events to the prediction bar icons private final Point mPredictionIconTouchDownPos = new Point(); - private int mContentMarginStart; // Normal container insets - private int mContainerInset; private int mPredictionBarHeight; private int mLastRecyclerViewScrollPos = -1; @Thunk boolean mFocusPredictionBarOnFirstBind; + private SpannableStringBuilder mSearchQueryBuilder = null; + private CheckLongPressHelper mPredictionIconCheckForLongPress; private View mPredictionIconUnderTouch; - private AppSearchManager mSearchManager; - public AllAppsContainerView(Context context) { this(context, null); } @@ -238,30 +121,24 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc Resources res = context.getResources(); mLauncher = (Launcher) context; + mLayoutInflater = LayoutInflater.from(context); DeviceProfile grid = mLauncher.getDeviceProfile(); - - mContainerInset = res.getDimensionPixelSize(R.dimen.all_apps_container_inset); mPredictionBarHeight = (int) (grid.allAppsIconSizePx + grid.iconDrawablePaddingOriginalPx + Utilities.calculateTextHeight(grid.allAppsIconTextSizePx) + 2 * res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding) + - res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_bottom_padding)); - - mLayoutInflater = LayoutInflater.from(context); + 2 * res.getDimensionPixelSize(R.dimen.all_apps_prediction_bar_top_bottom_padding)); - mNumAppsPerRow = grid.allAppsNumCols; - mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols; - mApps = new AlphabeticalAppsList(context, mNumAppsPerRow, mNumPredictedAppsPerRow); + mApps = new AlphabeticalAppsList(context); mApps.setAdapterChangedCallback(this); - mAdapter = new AllAppsGridAdapter(context, mApps, mNumAppsPerRow, this, this, mLauncher, this); + mAdapter = new AllAppsGridAdapter(context, mApps, this, this, mLauncher, this); mAdapter.setEmptySearchText(res.getString(R.string.all_apps_loading_message)); - mAdapter.setNumAppsPerRow(mNumAppsPerRow); mAdapter.setPredictionRowHeight(mPredictionBarHeight); + mApps.setAdapter(mAdapter); mLayoutManager = mAdapter.getLayoutManager(); mItemDecoration = mAdapter.getItemDecoration(); - mContentMarginStart = mAdapter.getContentMarginStart(); - mApps.setAdapter(mAdapter); - mSearchManager = mApps.newSimpleAppSearchManager(); + mSearchQueryBuilder = new SpannableStringBuilder(); + Selection.setSelection(mSearchQueryBuilder, 0); } /** @@ -285,11 +162,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mApps.addApps(apps); } - public void setSearchManager(AppSearchManager searchManager) { - mSearchManager.cancel(true); - mSearchManager = searchManager; - } - /** * Updates existing apps in the list */ @@ -305,13 +177,23 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } /** - * Hides the header bar + * Sets the search bar that shows above the a-z list. */ - public void hideHeaderBar() { - mHeaderView.setVisibility(View.GONE); - mElevationController.disable(); - onUpdateBackgrounds(); - onUpdatePaddings(); + public void setSearchBarController(AllAppsSearchBarController searchController) { + if (mSearchBarController != null) { + throw new RuntimeException("Expected search bar controller to only be set once"); + } + mSearchBarController = searchController; + mSearchBarController.initialize(mApps, this); + + // Add the new search view to the layout + View searchBarView = searchController.getView(mSearchBarContainerView); + mSearchBarContainerView.addView(searchBarView); + mSearchBarContainerView.setVisibility(View.VISIBLE); + mSearchBarView = searchBarView; + setHasSearchBar(); + + updateBackgroundAndPaddings(); } /** @@ -325,28 +207,43 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc * Returns the content view used for the launcher transitions. */ public View getContentView() { - return mContentView; + return mContainerView; + } + + /** + * Returns the all apps search view. + */ + public View getSearchBarView() { + return mSearchBarView; } /** * Returns the reveal view used for the launcher transitions. */ public View getRevealView() { - return findViewById(R.id.apps_view_transition_overlay); + return mRevealView; + } + + /** + * Returns an new instance of the default app search controller. + */ + public AllAppsSearchBarController newDefaultAppSearchController() { + return new DefaultAppSearchController(getContext(), this, mAppsRecyclerView); } @Override protected void onFinishInflate() { + super.onFinishInflate(); boolean isRtl = Utilities.isRtl(getResources()); mAdapter.setRtl(isRtl); + mContent = findViewById(R.id.content); - // Work around the search box getting first focus and showing the cursor by - // proxying the focus from the content view to the recycler view directly - mContentView = (FrameLayout) findViewById(R.id.apps_list); - mContentView.setOnFocusChangeListener(new View.OnFocusChangeListener() { + // This is a focus listener that proxies focus from a view into the list view. This is to + // work around the search box from getting first focus and showing the cursor. + View.OnFocusChangeListener focusProxyListener = new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { - if (v == mContentView && hasFocus) { + if (hasFocus) { if (!mApps.getPredictedApps().isEmpty()) { // If the prediction bar is going to be bound, then defer focusing until // it is first bound @@ -360,52 +257,16 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } } } - }); - - // Fix the header view elevation if not dynamically calculating it - mHeaderView = findViewById(R.id.header); - mHeaderView.setOnClickListener(this); - - mElevationController = Utilities.isLmpOrAbove() ? - new HeaderElevationControllerVL(mHeaderView) : - new HeaderElevationControllerV16(mHeaderView); - if (!DYNAMIC_HEADER_ELEVATION) { - mElevationController.onScroll(getResources() - .getDimensionPixelSize(R.dimen.all_apps_header_scroll_to_elevation)); - } - - // Fix the prediction bar size - mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar); - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams(); - lp.height = mPredictionBarHeight; - - mSearchButtonView = mHeaderView.findViewById(R.id.search_button); - mSearchBarContainerView = findViewById(R.id.app_search_container); - mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button); - mDismissSearchButtonView.setOnClickListener(this); - mSearchBarEditView = (AllAppsSearchEditView) findViewById(R.id.apps_search_box); - if (mSearchBarEditView != null) { - mSearchBarEditView.addTextChangedListener(this); - mSearchBarEditView.setOnEditorActionListener(this); - if (DISMISS_SEARCH_ON_BACK) { - mSearchBarEditView.setOnBackKeyListener( - new AllAppsSearchEditView.OnBackKeyListener() { - @Override - public void onBackKey() { - // Only hide the search field if there is no query, or if there - // are no filtered results - String query = Utilities.trim( - mSearchBarEditView.getEditableText().toString()); - if (query.isEmpty() || mApps.hasNoFilteredResults()) { - hideSearchField(true, true); - } - } - }); - } - } - mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view); + }; + mSearchBarContainerView = (ViewGroup) findViewById(R.id.search_box_container); + mSearchBarContainerView.setOnFocusChangeListener(focusProxyListener); + mContainerView = findViewById(R.id.all_apps_container); + mContainerView.setOnFocusChangeListener(focusProxyListener); + mRevealView = findViewById(R.id.all_apps_reveal); + + // Load the all apps recycler view + mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.collection); mAppsRecyclerView.setApps(mApps); - mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow); mAppsRecyclerView.setPredictionBarHeight(mPredictionBarHeight); mAppsRecyclerView.setLayoutManager(mLayoutManager); mAppsRecyclerView.setAdapter(mAdapter); @@ -413,8 +274,18 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc if (mItemDecoration != null) { mAppsRecyclerView.addItemDecoration(mItemDecoration); } - onUpdateBackgrounds(); - onUpdatePaddings(); + + // Fix the prediction bar height + mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar); + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams(); + lp.height = mPredictionBarHeight; + + updateBackgroundAndPaddings(); + } + + @Override + public void onBoundsChanged(Rect newBounds) { + mLauncher.updateOverlayBounds(newBounds); } @Override @@ -422,6 +293,12 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc updatePredictionBarVisibility(); List predictedApps = mApps.getPredictedApps(); + + // Remove extra prediction icons + while (mPredictionBarView.getChildCount() > mNumPredictedAppsPerRow) { + mPredictionBarView.removeViewAt(mPredictionBarView.getChildCount() - 1); + } + int childCount = mPredictionBarView.getChildCount(); for (int i = 0; i < mNumPredictedAppsPerRow; i++) { BubbleTextView icon; @@ -455,95 +332,111 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } @Override - protected void onFixedBoundsUpdated() { - // Update the number of items in the grid + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Update the number of items in the grid before we measure the view + int availableWidth = !mContentBounds.isEmpty() ? mContentBounds.width() : + MeasureSpec.getSize(widthMeasureSpec); DeviceProfile grid = mLauncher.getDeviceProfile(); - if (grid.updateAppsViewNumCols(getContext().getResources(), mFixedBounds.width())) { + grid.updateAppsViewNumCols(getResources(), availableWidth); + if (mNumAppsPerRow != grid.allAppsNumCols || + mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) { mNumAppsPerRow = grid.allAppsNumCols; mNumPredictedAppsPerRow = grid.allAppsNumPredictiveCols; mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow); mAdapter.setNumAppsPerRow(mNumAppsPerRow); mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow); } + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** - * Update the padding of the Apps view and children. To ensure that the RecyclerView has the - * full width to handle touches right to the edge of the screen, we only apply the top and - * bottom padding to the AppsContainerView and then the left/right padding on the RecyclerView - * itself. In particular, the left/right padding is applied to the background of the view, - * and then additionally inset by the start margin. + * Update the background and padding of the Apps view and children. Instead of insetting the + * container view, we inset the background and padding of the recycler view to allow for the + * recycler view to handle touch events (for fast scrolling) all the way to the edge. */ @Override - protected void onUpdatePaddings() { + protected void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding) { boolean isRtl = Utilities.isRtl(getResources()); - boolean hasSearchBar = (mSearchBarEditView != null) && - (mSearchBarEditView.getVisibility() == View.VISIBLE); - // Set the background on the container, but let the recyclerView extend the full screen, - // so that the fast-scroller works on the edge as well. - mContentView.setPadding(0, 0, 0, 0); - - if (mFixedBounds.isEmpty()) { - // If there are no fixed bounds, then use the default padding and insets - setPadding(mInsets.left, mContainerInset + mInsets.top, mInsets.right, - mContainerInset + mInsets.bottom); - } else { - // If there are fixed bounds, then we update the padding to reflect the fixed bounds. - setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right, - mFixedBounds.bottom); - } - - // Update the apps recycler view, inset it by the container inset as well + // TODO: Use quantum_panel instead of quantum_panel_shape. + InsetDrawable background = new InsetDrawable( + getResources().getDrawable(R.drawable.quantum_panel_shape), padding.left, 0, + padding.right, 0); + mContainerView.setBackground(background); + mRevealView.setBackground(background.getConstantState().newDrawable()); + mAppsRecyclerView.updateBackgroundPadding(padding); + mAdapter.updateBackgroundPadding(padding); + + // Hack: We are going to let the recycler view take the full width, so reset the padding on + // the container to zero after setting the background and apply the top-bottom padding to + // the content view instead so that the launcher transition clips correctly. + mContent.setPadding(0, padding.top, 0, padding.bottom); + mContainerView.setPadding(0, 0, 0, 0); + + // Pad the recycler view by the background padding plus the start margin (for the section + // names) DeviceProfile grid = mLauncher.getDeviceProfile(); - int startMargin = grid.isPhone ? mContentMarginStart : 0; - int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset; + int startMargin = grid.isPhone ? getResources().getDimensionPixelSize( + R.dimen.all_apps_grid_view_start_margin) : mAppsRecyclerView.getScrollbarWidth(); if (isRtl) { - mAppsRecyclerView.setPadding(inset + mAppsRecyclerView.getScrollbarWidth(), 0, - inset + startMargin, 0); + mAppsRecyclerView.setPadding(padding.left + mAppsRecyclerView.getScrollbarWidth(), 0, + padding.right + startMargin, 0); } else { - mAppsRecyclerView.setPadding(inset + startMargin, 0, - inset + mAppsRecyclerView.getScrollbarWidth(), 0); + mAppsRecyclerView.setPadding(padding.left + startMargin, 0, + padding.right + mAppsRecyclerView.getScrollbarWidth(), 0); } - // Update the header bar - if (hasSearchBar) { - FrameLayout.LayoutParams lp = - (FrameLayout.LayoutParams) mHeaderView.getLayoutParams(); - lp.leftMargin = lp.rightMargin = inset; - mHeaderView.requestLayout(); + // Inset the search bar to fit its bounds above the container + if (mSearchBarView != null) { + Rect backgroundPadding = new Rect(); + if (mSearchBarView.getBackground() != null) { + mSearchBarView.getBackground().getPadding(backgroundPadding); + } + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) + mSearchBarContainerView.getLayoutParams(); + lp.leftMargin = searchBarBounds.left - backgroundPadding.left; + lp.topMargin = searchBarBounds.top - backgroundPadding.top; + lp.rightMargin = (getMeasuredWidth() - searchBarBounds.right) - backgroundPadding.right; + mSearchBarContainerView.requestLayout(); } + // Update the prediction bar insets as well + mPredictionBarView = (ViewGroup) findViewById(R.id.prediction_bar); FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPredictionBarView.getLayoutParams(); - lp.leftMargin = inset + mAppsRecyclerView.getScrollbarWidth(); - lp.rightMargin = inset + mAppsRecyclerView.getScrollbarWidth(); + lp.leftMargin = padding.left + mAppsRecyclerView.getScrollbarWidth(); + lp.rightMargin = padding.right + mAppsRecyclerView.getScrollbarWidth(); mPredictionBarView.requestLayout(); } - /** - * Update the background of the Apps view and children. - */ @Override - protected void onUpdateBackgrounds() { - int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset; - - // Update the background of the reveal view and list to be inset with the fixed bound - // insets instead of the default insets - // TODO: Use quantum_panel instead of quantum_panel_shape. - InsetDrawable background = new InsetDrawable( - getContext().getResources().getDrawable(R.drawable.quantum_panel_shape), - inset, 0, inset, 0); - mContentView.setBackground(background); - mAppsRecyclerView.updateBackgroundPadding(background); - mAdapter.updateBackgroundPadding(background); - mElevationController.updateBackgroundPadding(background); - getRevealView().setBackground(background.getConstantState().newDrawable()); + public boolean onPreDraw() { + if (mNumAppsPerRow > 0) { + // Update the position of the prediction bar to match the scroll of the all apps list + synchronizeToRecyclerViewScrollPosition(mAppsRecyclerView.getScrollPosition()); + } + return true; } @Override - public boolean onPreDraw() { - synchronizeToRecyclerViewScrollPosition(mAppsRecyclerView.getScrollPosition()); - return true; + public boolean dispatchKeyEvent(KeyEvent event) { + // Determine if the key event was actual text, if so, focus the search bar and then dispatch + // the key normally so that it can process this key event + if (!mSearchBarController.isSearchFieldFocused() && + event.getAction() == KeyEvent.ACTION_DOWN) { + final int unicodeChar = event.getUnicodeChar(); + final boolean isKeyNotWhitespace = unicodeChar > 0 && + !Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar); + if (isKeyNotWhitespace) { + boolean gotKey = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder, + event.getKeyCode(), event); + if (gotKey && mSearchQueryBuilder.length() > 0) { + mSearchBarController.focusSearchField(); + } + } + } + + return super.dispatchKeyEvent(event); } @Override @@ -569,15 +462,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc return false; } - @Override - public void onClick(View v) { - if (v == mHeaderView) { - showSearchField(); - } else if (v == mDismissSearchButtonView) { - hideSearchField(true, true); - } - } - @Override public boolean onLongClick(View v) { // Return early if this is not initiated from a touch @@ -660,70 +544,11 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } } - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // Do nothing - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - // Do nothing - } - - @Override - public void afterTextChanged(final Editable s) { - String queryText = s.toString(); - if (queryText.isEmpty()) { - mSearchManager.cancel(true); - mApps.setOrderedFilter(null); - } else { - String formatStr = getResources().getString(R.string.all_apps_no_search_results); - mAdapter.setEmptySearchText(String.format(formatStr, queryText)); - - mSearchManager.cancel(false); - mSearchManager.doSearch(queryText, this); - } - scrollToTop(); - } - - @Override - public void onSearchResult(ArrayList apps) { - if (apps != null) { - mApps.setOrderedFilter(apps); - } - } - - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (ALLOW_SINGLE_APP_LAUNCH && actionId == EditorInfo.IME_ACTION_DONE) { - // Skip the quick-launch if there isn't exactly one item - if (mApps.getSize() != 1) { - return false; - } - - List items = mApps.getAdapterItems(); - for (int i = 0; i < items.size(); i++) { - AlphabeticalAppsList.AdapterItem item = items.get(i); - if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) { - mAppsRecyclerView.getChildAt(i).performClick(); - getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0); - return true; - } - } - } - return false; - } - @Override public void onAdapterItemsChanged() { updatePredictionBarVisibility(); } - @Override - public View getContent() { - return null; - } - @Override public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { // Register for a pre-draw listener to synchronize the recycler view scroll to other views @@ -745,14 +570,12 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc @Override public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { - if (mSearchBarEditView != null) { - if (toWorkspace) { - hideSearchField(false, false); - } - } if (toWorkspace) { getViewTreeObserver().removeOnPreDrawListener(this); mLastRecyclerViewScrollPos = -1; + + // Reset the search bar after transitioning home + mSearchBarController.reset(); } } @@ -763,9 +586,6 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc private void synchronizeToRecyclerViewScrollPosition(int scrollY) { if (mLastRecyclerViewScrollPos != scrollY) { mLastRecyclerViewScrollPos = scrollY; - if (DYNAMIC_HEADER_ELEVATION) { - mElevationController.onScroll(scrollY); - } // Scroll the prediction bar with the contents of the recycler view mPredictionBarView.setTranslationY(-scrollY + mAppsRecyclerView.getPaddingTop()); @@ -806,9 +626,9 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } } - if (!mFixedBounds.isEmpty()) { + if (!mContentBounds.isEmpty()) { // Outset the fixed bounds and check if the touch is outside all apps - Rect tmpRect = new Rect(mFixedBounds); + Rect tmpRect = new Rect(mContentBounds); tmpRect.inset(-grid.allAppsIconSizePx / 2, 0); if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) { mBoundsCheckLastTouchDownPos.set(x, y); @@ -874,6 +694,29 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc return false; } + @Override + public void onSearchResult(String query, ArrayList apps) { + if (apps != null) { + if (apps.isEmpty()) { + String formatStr = getResources().getString(R.string.all_apps_no_search_results); + mAdapter.setEmptySearchText(String.format(formatStr, query)); + } else { + mAppsRecyclerView.scrollToTop(); + } + mApps.setOrderedFilter(apps); + } + } + + @Override + public void clearSearchResult() { + mApps.setOrderedFilter(null); + + // Clear the search query + mSearchQueryBuilder.clear(); + mSearchQueryBuilder.clearSpans(); + Selection.setSelection(mSearchQueryBuilder, 0); + } + @Override public void fillInLaunchSourceData(Bundle sourceData) { // Since the other cases are caught by the AllAppsRecyclerView LaunchSourceProvider, we just @@ -889,11 +732,11 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc private View findPredictedAppAtCoordinate(int x, int y) { Rect hitRect = new Rect(); - // Ensure we aren't hitting the search bar + // Ensure that are touching in the recycler view int[] coord = {x, y}; - Utilities.mapCoordInSelfToDescendent(mHeaderView, this, coord); - mHeaderView.getHitRect(hitRect); - if (hitRect.contains(coord[0], coord[1])) { + Utilities.mapCoordInSelfToDescendent(mAppsRecyclerView, this, coord); + mAppsRecyclerView.getHitRect(hitRect); + if (!hitRect.contains(coord[0], coord[1])) { return null; } @@ -914,95 +757,13 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc return null; } - /** - * Shows the search field. - */ - private void showSearchField() { - mSearchManager.connect(); - - // Show the search bar and focus the search - final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP, - getContext().getResources().getDisplayMetrics()); - mSearchBarContainerView.setVisibility(View.VISIBLE); - mSearchBarContainerView.setAlpha(0f); - mSearchBarContainerView.setTranslationX(translationX); - mSearchBarContainerView.animate() - .alpha(1f) - .translationX(0) - .setDuration(FADE_IN_DURATION) - .withLayer() - .withEndAction(new Runnable() { - @Override - public void run() { - mSearchBarEditView.requestFocus(); - getInputMethodManager().showSoftInput(mSearchBarEditView, - InputMethodManager.SHOW_IMPLICIT); - } - }); - mSearchButtonView.animate() - .alpha(0f) - .translationX(-translationX) - .setDuration(FADE_OUT_DURATION) - .withLayer(); - } - - /** - * Hides the search field. - */ - @Thunk void hideSearchField(boolean animated, final boolean returnFocusToRecyclerView) { - mSearchManager.cancel(true); - - final boolean resetTextField = mSearchBarEditView.getText().toString().length() > 0; - final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP, - getContext().getResources().getDisplayMetrics()); - if (animated) { - // Hide the search bar and focus the recycler view - mSearchBarContainerView.animate() - .alpha(0f) - .translationX(0) - .setDuration(FADE_IN_DURATION) - .withLayer() - .withEndAction(new Runnable() { - @Override - public void run() { - mSearchBarContainerView.setVisibility(View.INVISIBLE); - if (resetTextField) { - mSearchBarEditView.setText(""); - } - mApps.setOrderedFilter(null); - if (returnFocusToRecyclerView) { - mAppsRecyclerView.requestFocus(); - } - } - }); - mSearchButtonView.setTranslationX(-translationX); - mSearchButtonView.animate() - .alpha(1f) - .translationX(0) - .setDuration(FADE_OUT_DURATION) - .withLayer(); - } else { - mSearchBarContainerView.setVisibility(View.INVISIBLE); - if (resetTextField) { - mSearchBarEditView.setText(""); - } - mApps.setOrderedFilter(null); - mSearchButtonView.setAlpha(1f); - mSearchButtonView.setTranslationX(0f); - if (returnFocusToRecyclerView) { - mAppsRecyclerView.requestFocus(); - } - } - getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0); - } - /** * Updates the visibility of the prediction bar. * @return whether the prediction bar is visible */ private boolean updatePredictionBarVisibility() { - boolean showPredictionBar = !mApps.getPredictedApps().isEmpty() && (!mApps.hasFilter() || - mSearchBarEditView.getEditableText().toString().isEmpty()); + boolean showPredictionBar = !mApps.getPredictedApps().isEmpty() && + (!mApps.hasFilter() || mSearchBarController.shouldShowPredictionBar()); if (showPredictionBar) { mPredictionBarView.setVisibility(View.VISIBLE); } else if (!showPredictionBar) { @@ -1010,11 +771,4 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc } return showPredictionBar; } - - /** - * Returns an input method manager. - */ - @Thunk InputMethodManager getInputMethodManager() { - return (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - } } diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 4b8b2dfc8..dc0d27cd2 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -109,6 +109,7 @@ class AllAppsGridAdapter extends RecyclerView.Adapter mCachedSectionBounds = new HashMap<>(); @@ -121,10 +122,17 @@ class AllAppsGridAdapter extends RecyclerView.Adapter items = mApps.getAdapterItems(); boolean hasDrawnPredictedAppsDivider = false; @@ -171,8 +179,8 @@ class AllAppsGridAdapter extends RecyclerView.Adapter items = mApps.getAdapterItems(); - // Skip early if there are no items. - if (items.isEmpty()) { + // Skip early if there are no items or we haven't been measured + if (items.isEmpty() || mNumAppsPerRow == 0) { verticalScrollbarBounds.setEmpty(); return; } @@ -242,8 +234,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView int height = getHeight() - getPaddingTop() - getPaddingBottom(); int totalScrollHeight = rowCount * scrollPosState.rowHeight + predictionBarHeight; if (totalScrollHeight > height) { - int scrollbarHeight = Math.max(mScrollbarMinHeight, - (int) (height / ((float) totalScrollHeight / height))); + int scrollbarHeight = (int) (height / ((float) totalScrollHeight / height)); // Calculate the position and size of the scroll bar if (Utilities.isRtl(getResources())) { @@ -277,8 +268,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView stateOut.rowTopOffset = -1; stateOut.rowHeight = -1; - // Return early if there are no items - if (items.isEmpty()) { + // Return early if there are no items or we haven't been measured + if (items.isEmpty() || mNumAppsPerRow == 0) { return; } diff --git a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java new file mode 100644 index 000000000..3cacd9d69 --- /dev/null +++ b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.allapps; + +import android.content.ComponentName; +import android.graphics.Rect; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; + +/** + * An interface to a search box that AllApps can command. + */ +public abstract class AllAppsSearchBarController { + + protected AlphabeticalAppsList mApps; + protected Callbacks mCb; + + /** + * Sets the references to the apps model and the search result callback. + */ + public final void initialize(AlphabeticalAppsList apps, Callbacks cb) { + mApps = apps; + mCb = cb; + onInitialize(); + } + + /** + * To be overridden by subclasses. This method will get called when the controller is set, + * before getView(). + */ + protected abstract void onInitialize(); + + /** + * Returns the search bar view. + * @param parent the parent to attach the search bar view to. + */ + public abstract View getView(ViewGroup parent); + + /** + * Focuses the search field to handle key events. + */ + public abstract void focusSearchField(); + + /** + * Returns whether the search field is focused. + */ + public abstract boolean isSearchFieldFocused(); + + /** + * Resets the search bar state. + */ + public abstract void reset(); + + /** + * Returns whether the prediction bar should currently be visible depending on the state of + * the search bar. + */ + public abstract boolean shouldShowPredictionBar(); + + /** + * Callback for getting search results. + */ + public interface Callbacks { + + /** + * Called when the bounds of the search bar has changed. + */ + void onBoundsChanged(Rect newBounds); + + /** + * Called when the search is complete. + * + * @param apps sorted list of matching components or null if in case of failure. + */ + void onSearchResult(String query, ArrayList apps); + + /** + * Called when the search results should be cleared. + */ + void clearSearchResult(); + } +} \ No newline at end of file diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index e284f77c4..a0cf5b6dc 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -235,11 +235,10 @@ public class AlphabeticalAppsList { private int mNumAppsPerRow; private int mNumPredictedAppsPerRow; - public AlphabeticalAppsList(Context context, int numAppsPerRow, int numPredictedAppsPerRow) { + public AlphabeticalAppsList(Context context) { mLauncher = (Launcher) context; mIndexer = new AlphabeticIndexCompat(context); mAppNameComparator = new AppNameComparator(context); - setNumAppsPerRow(numAppsPerRow, numPredictedAppsPerRow); } /** @@ -249,10 +248,6 @@ public class AlphabeticalAppsList { mAdapterChangedCallback = cb; } - public SimpleAppSearchManagerImpl newSimpleAppSearchManager() { - return new SimpleAppSearchManagerImpl(mApps); - } - /** * Sets the number of apps per row. Used only for AppsContainerView.SECTIONED_GRID_COALESCED. */ @@ -269,7 +264,7 @@ public class AlphabeticalAppsList { mNumAppsPerRow = numAppsPerRow; mNumPredictedAppsPerRow = numPredictedAppsPerRow; - onAppsUpdated(); + updateAdapterItems(); } /** @@ -279,6 +274,13 @@ public class AlphabeticalAppsList { mAdapter = adapter; } + /** + * Returns all the apps. + */ + public List getApps() { + return mApps; + } + /** * Returns sections of all the current filtered applications. */ @@ -597,6 +599,11 @@ public class AlphabeticalAppsList { * Merges multiple sections to reduce visual raggedness. */ private void mergeSections() { + // Ignore merging until we have a valid row size + if (mNumAppsPerRow == 0) { + return; + } + // Go through each section and try and merge some of the sections if (AllAppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) { int sectionAppCount = 0; diff --git a/src/com/android/launcher3/allapps/AppSearchManager.java b/src/com/android/launcher3/allapps/AppSearchManager.java deleted file mode 100644 index b6aa22341..000000000 --- a/src/com/android/launcher3/allapps/AppSearchManager.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.allapps; - -import android.content.ComponentName; - -import java.util.ArrayList; - -/** - * Interface for handling app search. - */ -public interface AppSearchManager { - - /** - * Called when the search is about to be used. This method is optional for making a query but - * calling this appropriately can improve the initial response time. - */ - void connect(); - - /** - * Cancels all pending search requests. - * - * @param interruptActiveRequests if true, any active requests which are already executing will - * be invalidated, and the corresponding results will not be sent. The client should usually - * set this to true, before beginning a new search session. - */ - void cancel(boolean interruptActiveRequests); - - /** - * Performs a search - */ - void doSearch(String query, AppSearchResultCallback callback); - - /** - * Callback for getting search results. - */ - public interface AppSearchResultCallback { - - /** - * Called when the search is complete. - * - * @param apps sorted list of matching components or null if in case of failure. - */ - void onSearchResult(ArrayList apps); - } -} diff --git a/src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java new file mode 100644 index 000000000..28854be0e --- /dev/null +++ b/src/com/android/launcher3/allapps/DefaultAppSearchAlgorithm.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.allapps; + +import android.content.ComponentName; +import android.os.Handler; +import com.android.launcher3.AppInfo; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +/** + * The default search implementation. + */ +public class DefaultAppSearchAlgorithm { + + private static final Pattern SPLIT_PATTERN = Pattern.compile("[\\s|\\p{javaSpaceChar}]+"); + + private final List mApps; + private final Handler mResultHandler; + + public DefaultAppSearchAlgorithm(List apps) { + mApps = apps; + mResultHandler = new Handler(); + } + + public void cancel(boolean interruptActiveRequests) { + if (interruptActiveRequests) { + mResultHandler.removeCallbacksAndMessages(null); + } + } + + public void doSearch(final String query, + final AllAppsSearchBarController.Callbacks callback) { + // Do an intersection of the words in the query and each title, and filter out all the + // apps that don't match all of the words in the query. + final String queryTextLower = query.toLowerCase(); + final String[] queryWords = SPLIT_PATTERN.split(queryTextLower); + final ArrayList result = new ArrayList<>(); + int total = mApps.size(); + + for (int i = 0; i < total; i++) { + AppInfo info = mApps.get(i); + if (!result.contains(info.componentName) && matches(info, queryWords)) { + result.add(info.componentName); + } + } + mResultHandler.post(new Runnable() { + + @Override + public void run() { + callback.onSearchResult(query, result); + } + }); + } + + private boolean matches(AppInfo info, String[] queryWords) { + String title = info.title.toString(); + String[] words = SPLIT_PATTERN.split(title.toLowerCase()); + for (int qi = 0; qi < queryWords.length; qi++) { + boolean foundMatch = false; + for (int i = 0; i < words.length; i++) { + if (words[i].startsWith(queryWords[qi])) { + foundMatch = true; + break; + } + } + if (!foundMatch) { + // If there is a word in the query that does not match any words in this + // title, so skip it. + return false; + } + } + return true; + } + +} diff --git a/src/com/android/launcher3/allapps/DefaultAppSearchController.java b/src/com/android/launcher3/allapps/DefaultAppSearchController.java new file mode 100644 index 000000000..1601c62df --- /dev/null +++ b/src/com/android/launcher3/allapps/DefaultAppSearchController.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.allapps; + +import android.content.Context; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.TextView; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.util.Thunk; + +import java.util.List; + + +/** + * The default search controller. + */ +final class DefaultAppSearchController extends AllAppsSearchBarController + implements TextWatcher, TextView.OnEditorActionListener, View.OnClickListener { + + private static final boolean ALLOW_SINGLE_APP_LAUNCH = true; + + private static final int FADE_IN_DURATION = 175; + private static final int FADE_OUT_DURATION = 100; + private static final int SEARCH_TRANSLATION_X_DP = 18; + + private final Context mContext; + @Thunk final InputMethodManager mInputMethodManager; + + private DefaultAppSearchAlgorithm mSearchManager; + + private ViewGroup mContainerView; + private View mSearchView; + @Thunk View mSearchBarContainerView; + private View mSearchButtonView; + private View mDismissSearchButtonView; + @Thunk AllAppsSearchEditView mSearchBarEditView; + @Thunk AllAppsRecyclerView mAppsRecyclerView; + private Runnable mFocusRecyclerViewRunnable = new Runnable() { + @Override + public void run() { + mAppsRecyclerView.requestFocus(); + } + }; + + public DefaultAppSearchController(Context context, ViewGroup containerView, + AllAppsRecyclerView appsRecyclerView) { + mContext = context; + mInputMethodManager = (InputMethodManager) + mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + mContainerView = containerView; + mAppsRecyclerView = appsRecyclerView; + } + + @Override + public View getView(ViewGroup parent) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + mSearchView = inflater.inflate(R.layout.all_apps_search_bar, parent, false); + mSearchView.setOnClickListener(this); + + mSearchButtonView = mSearchView.findViewById(R.id.search_button); + mSearchBarContainerView = mSearchView.findViewById(R.id.search_container); + mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button); + mDismissSearchButtonView.setOnClickListener(this); + mSearchBarEditView = (AllAppsSearchEditView) + mSearchBarContainerView.findViewById(R.id.search_box); + mSearchBarEditView.addTextChangedListener(this); + mSearchBarEditView.setOnEditorActionListener(this); + mSearchBarEditView.setOnBackKeyListener( + new AllAppsSearchEditView.OnBackKeyListener() { + @Override + public void onBackKey() { + // Only hide the search field if there is no query, or if there + // are no filtered results + String query = Utilities.trim( + mSearchBarEditView.getEditableText().toString()); + if (query.isEmpty() || mApps.hasNoFilteredResults()) { + hideSearchField(true, mFocusRecyclerViewRunnable); + } + } + }); + return mSearchView; + } + + @Override + public void focusSearchField() { + mSearchBarEditView.requestFocus(); + showSearchField(); + } + + @Override + public boolean isSearchFieldFocused() { + return mSearchBarEditView.isFocused(); + } + + @Override + protected void onInitialize() { + mSearchManager = new DefaultAppSearchAlgorithm(mApps.getApps()); + } + + @Override + public void reset() { + hideSearchField(false, null); + } + + @Override + public boolean shouldShowPredictionBar() { + // Keep showing the prediction bar if the input query is empty + return mSearchBarEditView.getEditableText().toString().isEmpty(); + } + + @Override + public void onClick(View v) { + if (v == mSearchView) { + showSearchField(); + } else if (v == mDismissSearchButtonView) { + hideSearchField(true, mFocusRecyclerViewRunnable); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Do nothing + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // Do nothing + } + + @Override + public void afterTextChanged(final Editable s) { + String query = s.toString(); + if (query.isEmpty()) { + mSearchManager.cancel(true); + mCb.clearSearchResult(); + } else { + mSearchManager.cancel(false); + mSearchManager.doSearch(query, mCb); + } + } + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + // Skip if we disallow app-launch-on-enter + if (!ALLOW_SINGLE_APP_LAUNCH) { + return false; + } + // Skip if it's not the right action + if (actionId != EditorInfo.IME_ACTION_DONE) { + return false; + } + // Skip if there isn't exactly one item + if (mApps.getSize() != 1) { + return false; + } + // If there is exactly one icon, then quick-launch it + List items = mApps.getAdapterItems(); + for (int i = 0; i < items.size(); i++) { + AlphabeticalAppsList.AdapterItem item = items.get(i); + if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE) { + mAppsRecyclerView.getChildAt(i).performClick(); + mInputMethodManager.hideSoftInputFromWindow( + mContainerView.getWindowToken(), 0); + return true; + } + } + return false; + } + + /** + * Focuses the search field. + */ + private void showSearchField() { + // Show the search bar and focus the search + final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP, + mContext.getResources().getDisplayMetrics()); + mSearchBarContainerView.setVisibility(View.VISIBLE); + mSearchBarContainerView.setAlpha(0f); + mSearchBarContainerView.setTranslationX(translationX); + mSearchBarContainerView.animate() + .alpha(1f) + .translationX(0) + .setDuration(FADE_IN_DURATION) + .withLayer() + .withEndAction(new Runnable() { + @Override + public void run() { + mSearchBarEditView.requestFocus(); + mInputMethodManager.showSoftInput(mSearchBarEditView, + InputMethodManager.SHOW_IMPLICIT); + } + }); + mSearchButtonView.animate() + .alpha(0f) + .translationX(-translationX) + .setDuration(FADE_OUT_DURATION) + .withLayer(); + } + + /** + * Unfocuses the search field. + */ + @Thunk void hideSearchField(boolean animated, final Runnable postAnimationRunnable) { + mSearchManager.cancel(true); + + final boolean resetTextField = mSearchBarEditView.getText().toString().length() > 0; + final int translationX = Utilities.pxFromDp(SEARCH_TRANSLATION_X_DP, + mContext.getResources().getDisplayMetrics()); + if (animated) { + // Hide the search bar and focus the recycler view + mSearchBarContainerView.animate() + .alpha(0f) + .translationX(0) + .setDuration(FADE_IN_DURATION) + .withLayer() + .withEndAction(new Runnable() { + @Override + public void run() { + mSearchBarContainerView.setVisibility(View.INVISIBLE); + if (resetTextField) { + mSearchBarEditView.setText(""); + } + mCb.clearSearchResult(); + if (postAnimationRunnable != null) { + postAnimationRunnable.run(); + } + } + }); + mSearchButtonView.setTranslationX(-translationX); + mSearchButtonView.animate() + .alpha(1f) + .translationX(0) + .setDuration(FADE_OUT_DURATION) + .withLayer(); + } else { + mSearchBarContainerView.setVisibility(View.INVISIBLE); + if (resetTextField) { + mSearchBarEditView.setText(""); + } + mCb.clearSearchResult(); + mSearchButtonView.setAlpha(1f); + mSearchButtonView.setTranslationX(0f); + if (postAnimationRunnable != null) { + postAnimationRunnable.run(); + } + } + mInputMethodManager.hideSoftInputFromWindow(mContainerView.getWindowToken(), 0); + } +} diff --git a/src/com/android/launcher3/allapps/SimpleAppSearchManagerImpl.java b/src/com/android/launcher3/allapps/SimpleAppSearchManagerImpl.java deleted file mode 100644 index e8a31b546..000000000 --- a/src/com/android/launcher3/allapps/SimpleAppSearchManagerImpl.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.allapps; - -import android.content.ComponentName; -import android.os.Handler; - -import com.android.launcher3.AppInfo; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; - -/** - * An {@link AppSearchManager} which does label matching on the UI thread. - */ -public class SimpleAppSearchManagerImpl implements AppSearchManager { - - private static final Pattern SPLIT_PATTERN = Pattern.compile("[\\s|\\p{javaSpaceChar}]+"); - - private final List mApps; - private final Handler mResultHandler; - - public SimpleAppSearchManagerImpl(List apps) { - mApps = apps; - mResultHandler = new Handler(); - } - - @Override - public void connect() { - // No op - } - - @Override - public void cancel(boolean interruptActiveRequests) { - if (interruptActiveRequests) { - mResultHandler.removeCallbacksAndMessages(null); - } - } - - @Override - public void doSearch(String query, final AppSearchResultCallback callback) { - // Do an intersection of the words in the query and each title, and filter out all the - // apps that don't match all of the words in the query. - final String queryTextLower = query.toLowerCase(); - final String[] queryWords = SPLIT_PATTERN.split(queryTextLower); - final ArrayList result = new ArrayList(); - int total = mApps.size(); - - for (int i = 0; i < total; i++) { - AppInfo info = mApps.get(i); - if (!result.contains(info.componentName) && matches(info, queryWords)) { - result.add(info.componentName); - } - } - mResultHandler.post(new Runnable() { - - @Override - public void run() { - callback.onSearchResult(result); - } - }); - } - - private boolean matches(AppInfo info, String[] queryWords) { - String title = info.title.toString(); - String[] words = SPLIT_PATTERN.split(title.toLowerCase()); - for (int qi = 0; qi < queryWords.length; qi++) { - boolean foundMatch = false; - for (int i = 0; i < words.length; i++) { - if (words[i].startsWith(queryWords[qi])) { - foundMatch = true; - break; - } - } - if (!foundMatch) { - // If there is a word in the query that does not match any words in this - // title, so skip it. - return false; - } - } - return true; - } - -} diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java index 51f2a5f05..500311add 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerView.java @@ -66,6 +66,7 @@ public class WidgetsContainerView extends BaseContainerView private IconCache mIconCache; /* Recycler view related member variables */ + private View mContent; private WidgetsRecyclerView mView; private WidgetsListAdapter mAdapter; @@ -98,6 +99,7 @@ public class WidgetsContainerView extends BaseContainerView @Override protected void onFinishInflate() { + mContent = findViewById(R.id.content); mView = (WidgetsRecyclerView) findViewById(R.id.widgets_list_view); mView.setAdapter(mAdapter); @@ -112,7 +114,6 @@ public class WidgetsContainerView extends BaseContainerView }); mPadding.set(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom()); - onUpdatePaddings(); } // @@ -335,33 +336,18 @@ public class WidgetsContainerView extends BaseContainerView // @Override - protected void onUpdatePaddings() { - if (mFixedBounds.isEmpty()) { - // If there are no fixed bounds, then use the default padding and insets - setPadding(mPadding.left + mInsets.left, mPadding.top + mInsets.top, - mPadding.right + mInsets.right, mPadding.bottom + mInsets.bottom); - } else { - // If there are fixed bounds, then we update the padding to reflect the fixed bounds. - setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right, - mFixedBounds.bottom); - } - - int inset = mFixedBounds.isEmpty() ? mView.getScrollbarWidth() : mFixedBoundsContainerInset; - mView.setPadding(inset + mView.getScrollbarWidth(), inset, - inset, inset); - } - - @Override - protected void onUpdateBackgrounds() { - InsetDrawable background; - // Update the background of the reveal view and list to be inset with the fixed bound - // insets instead of the default insets - // TODO: Use quantum_panel instead of quantum_panel_shape. - int inset = mFixedBounds.isEmpty() ? mView.getScrollbarWidth() : mFixedBoundsContainerInset; - background = new InsetDrawable( - getContext().getResources().getDrawable(R.drawable.quantum_panel_shape), - inset, 0, inset, 0); - mView.updateBackgroundPadding(background); + protected void onUpdateBackgroundAndPaddings(Rect searchBarBounds, Rect padding) { + // Apply the top-bottom padding to the content itself so that the launcher transition is + // clipped correctly + mContent.setPadding(0, padding.top, 0, padding.bottom); + + // TODO: Use quantum_panel_dark instead of quantum_panel_shape_dark. + InsetDrawable background = new InsetDrawable( + getResources().getDrawable(R.drawable.quantum_panel_shape_dark), padding.left, 0, + padding.right, 0); + mView.setBackground(background); + getRevealView().setBackground(background.getConstantState().newDrawable()); + mView.updateBackgroundPadding(padding); } /** diff --git a/src/com/android/launcher3/widget/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/WidgetsRecyclerView.java index 9d265f87e..fa7e2f0a2 100644 --- a/src/com/android/launcher3/widget/WidgetsRecyclerView.java +++ b/src/com/android/launcher3/widget/WidgetsRecyclerView.java @@ -18,11 +18,9 @@ package com.android.launcher3.widget; import android.content.Context; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.support.v7.widget.LinearLayoutManager; import android.util.AttributeSet; import android.view.View; - import com.android.launcher3.BaseRecyclerView; import com.android.launcher3.Utilities; import com.android.launcher3.model.WidgetsModel; @@ -35,7 +33,6 @@ public class WidgetsRecyclerView extends BaseRecyclerView { private static final String TAG = "WidgetsRecyclerView"; private WidgetsModel mWidgets; - private Rect mBackgroundPadding = new Rect(); public WidgetsRecyclerView(Context context) { this(context, null); @@ -61,10 +58,6 @@ public class WidgetsRecyclerView extends BaseRecyclerView { addOnItemTouchListener(this); } - public void updateBackgroundPadding(Drawable background) { - background.getPadding(mBackgroundPadding); - } - /** * Sets the widget model in this view, used to determine the fast scroll position. */ -- cgit v1.2.3