blob: b8d30d0812af17a601931e880a034f6e1b734511 [file] [log] [blame]
Winson Chung93f98ea2015-03-10 16:28:47 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Winson Chungb745afb2015-03-02 11:51:23 -080016package com.android.launcher3;
17
Winson Chung4ac30062015-05-08 17:34:17 -070018import android.content.ComponentName;
Winson Chungb745afb2015-03-02 11:51:23 -080019import android.content.Context;
Winson Chung888b3a12015-03-13 11:14:16 -070020import android.content.res.Resources;
Winson Chungb745afb2015-03-02 11:51:23 -080021import android.graphics.Point;
22import android.graphics.Rect;
Winson Chung0f785722015-04-08 10:27:49 -070023import android.graphics.drawable.InsetDrawable;
Winson Chung93f98ea2015-03-10 16:28:47 -070024import android.support.v7.widget.RecyclerView;
25import android.text.Editable;
26import android.text.TextWatcher;
Winson Chungb745afb2015-03-02 11:51:23 -080027import android.util.AttributeSet;
Winson Chung93f98ea2015-03-10 16:28:47 -070028import android.view.KeyEvent;
Winson Chungb745afb2015-03-02 11:51:23 -080029import android.view.MotionEvent;
30import android.view.View;
Winson Chung0f785722015-04-08 10:27:49 -070031import android.view.ViewConfiguration;
Winson Chung93f98ea2015-03-10 16:28:47 -070032import android.view.inputmethod.EditorInfo;
33import android.view.inputmethod.InputMethodManager;
34import android.widget.EditText;
Winson Chung0f785722015-04-08 10:27:49 -070035import android.widget.LinearLayout;
Winson Chungb745afb2015-03-02 11:51:23 -080036import android.widget.TextView;
Adam Cohen091440a2015-03-18 14:16:05 -070037import com.android.launcher3.util.Thunk;
38
Winson Chungb745afb2015-03-02 11:51:23 -080039import java.util.List;
Winson Chung82b016c2015-05-08 17:00:10 -070040import java.util.regex.Pattern;
Winson Chungb745afb2015-03-02 11:51:23 -080041
42
43/**
Winson Chung94804152015-05-08 13:06:44 -070044 * The all apps view container.
Winson Chungb745afb2015-03-02 11:51:23 -080045 */
Winson Chung94804152015-05-08 13:06:44 -070046public class AppsContainerView extends BaseContainerView implements DragSource, Insettable,
47 TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener,
Winson Chung83f59ab2015-05-05 17:21:58 -070048 View.OnClickListener, View.OnLongClickListener {
49
50 public static final boolean GRID_MERGE_SECTIONS = true;
Winson Chung83f59ab2015-05-05 17:21:58 -070051 public static final boolean GRID_HIDE_SECTION_HEADERS = false;
Winson Chungb745afb2015-03-02 11:51:23 -080052
Winson Chung93f98ea2015-03-10 16:28:47 -070053 private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
Winson Chung83f59ab2015-05-05 17:21:58 -070054 private static final boolean DYNAMIC_HEADER_ELEVATION = false;
Winson Chungde34aa42015-05-07 18:21:28 -070055 private static final boolean DISMISS_SEARCH_ON_BACK = true;
Winson Chung83f59ab2015-05-05 17:21:58 -070056 private static final float HEADER_ELEVATION_DP = 4;
57 private static final int FADE_IN_DURATION = 175;
Winson Chungde34aa42015-05-07 18:21:28 -070058 private static final int FADE_OUT_DURATION = 100;
59 private static final int SEARCH_TRANSLATION_X_DP = 18;
Winson Chungb745afb2015-03-02 11:51:23 -080060
Winson Chung82b016c2015-05-08 17:00:10 -070061 private static final Pattern SPLIT_PATTERN = Pattern.compile("[\\s|\\p{javaSpaceChar}]+");
62
Adam Cohen091440a2015-03-18 14:16:05 -070063 @Thunk Launcher mLauncher;
64 @Thunk AlphabeticalAppsList mApps;
Winson Chung83f59ab2015-05-05 17:21:58 -070065 private AppsGridAdapter mAdapter;
Winson Chung93f98ea2015-03-10 16:28:47 -070066 private RecyclerView.LayoutManager mLayoutManager;
67 private RecyclerView.ItemDecoration mItemDecoration;
Winson Chung75cc8252015-04-10 10:19:58 -070068
69 private LinearLayout mContentView;
70 @Thunk AppsContainerRecyclerView mAppsRecyclerView;
Winson Chung83f59ab2015-05-05 17:21:58 -070071 private View mHeaderView;
72 private View mSearchBarContainerView;
73 private View mSearchButtonView;
74 private View mDismissSearchButtonView;
Winson Chungde34aa42015-05-07 18:21:28 -070075 private AppsContainerSearchEditTextView mSearchBarEditView;
Winson Chung83f59ab2015-05-05 17:21:58 -070076
Winson Chung93f98ea2015-03-10 16:28:47 -070077 private int mNumAppsPerRow;
Winson Chung466663e2015-04-17 12:11:01 -070078 private Point mLastTouchDownPos = new Point(-1, -1);
79 private Point mLastTouchPos = new Point();
Winson Chung93f98ea2015-03-10 16:28:47 -070080 private int mContentMarginStart;
Winson Chung0f785722015-04-08 10:27:49 -070081 // Normal container insets
82 private int mContainerInset;
Winson Chung83f59ab2015-05-05 17:21:58 -070083 // RecyclerView scroll position
84 @Thunk int mRecyclerViewScrollY;
Winson Chungb745afb2015-03-02 11:51:23 -080085
86 public AppsContainerView(Context context) {
87 this(context, null);
88 }
89
90 public AppsContainerView(Context context, AttributeSet attrs) {
91 this(context, attrs, 0);
92 }
93
94 public AppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
Winson Chungd0930652015-03-24 16:28:44 -070095 super(context, attrs, defStyleAttr);
Winson Chungb745afb2015-03-02 11:51:23 -080096 LauncherAppState app = LauncherAppState.getInstance();
97 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
Winson Chung888b3a12015-03-13 11:14:16 -070098 Resources res = context.getResources();
Winson Chungb745afb2015-03-02 11:51:23 -080099
Winson Chung0f785722015-04-08 10:27:49 -0700100 mContainerInset = context.getResources().getDimensionPixelSize(
101 R.dimen.apps_container_inset);
Winson Chungb745afb2015-03-02 11:51:23 -0800102 mLauncher = (Launcher) context;
Winson Chung83f59ab2015-05-05 17:21:58 -0700103 mNumAppsPerRow = grid.appsViewNumCols;
104 mApps = new AlphabeticalAppsList(context, mNumAppsPerRow);
105 mAdapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, mLauncher, this);
106 mAdapter.setEmptySearchText(res.getString(R.string.loading_apps_message));
107 mAdapter.setNumAppsPerRow(mNumAppsPerRow);
108 mLayoutManager = mAdapter.getLayoutManager();
109 mItemDecoration = mAdapter.getItemDecoration();
110 mContentMarginStart = mAdapter.getContentMarginStart();
Winson Chung93f98ea2015-03-10 16:28:47 -0700111 mApps.setAdapter(mAdapter);
Winson Chungb745afb2015-03-02 11:51:23 -0800112 }
113
114 /**
Winson Chung4ac30062015-05-08 17:34:17 -0700115 * Sets the current set of predicted apps.
116 */
117 public void setPredictedApps(List<ComponentName> apps) {
118 mApps.setPredictedApps(apps);
119 }
120
121 /**
Winson Chungb745afb2015-03-02 11:51:23 -0800122 * Sets the current set of apps.
123 */
124 public void setApps(List<AppInfo> apps) {
125 mApps.setApps(apps);
126 }
127
128 /**
129 * Adds new apps to the list.
130 */
131 public void addApps(List<AppInfo> apps) {
132 mApps.addApps(apps);
133 }
134
135 /**
136 * Updates existing apps in the list
137 */
138 public void updateApps(List<AppInfo> apps) {
139 mApps.updateApps(apps);
140 }
141
142 /**
143 * Removes some apps from the list.
144 */
145 public void removeApps(List<AppInfo> apps) {
146 mApps.removeApps(apps);
147 }
148
149 /**
Winson Chung83f59ab2015-05-05 17:21:58 -0700150 * Hides the header bar
Winson Chung0f785722015-04-08 10:27:49 -0700151 */
Winson Chung83f59ab2015-05-05 17:21:58 -0700152 public void hideHeaderBar() {
153 mHeaderView.setVisibility(View.GONE);
Winson Chung94804152015-05-08 13:06:44 -0700154 onUpdateBackgrounds();
155 onUpdatePaddings();
Winson Chung0f785722015-04-08 10:27:49 -0700156 }
157
158 /**
Winson Chungb745afb2015-03-02 11:51:23 -0800159 * Scrolls this list view to the top.
160 */
161 public void scrollToTop() {
Winson Chung75cc8252015-04-10 10:19:58 -0700162 mAppsRecyclerView.scrollToPosition(0);
Winson Chung83f59ab2015-05-05 17:21:58 -0700163 mRecyclerViewScrollY = 0;
Winson Chungb745afb2015-03-02 11:51:23 -0800164 }
165
166 /**
167 * Returns the content view used for the launcher transitions.
168 */
169 public View getContentView() {
Winson Chung75cc8252015-04-10 10:19:58 -0700170 return mContentView;
Winson Chungb745afb2015-03-02 11:51:23 -0800171 }
172
173 /**
174 * Returns the reveal view used for the launcher transitions.
175 */
176 public View getRevealView() {
Winson Chung93f98ea2015-03-10 16:28:47 -0700177 return findViewById(R.id.apps_view_transition_overlay);
Winson Chungb745afb2015-03-02 11:51:23 -0800178 }
179
180 @Override
181 protected void onFinishInflate() {
Winson Chung93f98ea2015-03-10 16:28:47 -0700182 boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
183 LAYOUT_DIRECTION_RTL);
Winson Chung83f59ab2015-05-05 17:21:58 -0700184 mAdapter.setRtl(isRtl);
Winson Chung75cc8252015-04-10 10:19:58 -0700185
186 // Work around the search box getting first focus and showing the cursor by
187 // proxying the focus from the content view to the recycler view directly
188 mContentView = (LinearLayout) findViewById(R.id.apps_list);
189 mContentView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
190 @Override
191 public void onFocusChange(View v, boolean hasFocus) {
192 if (v == mContentView && hasFocus) {
193 mAppsRecyclerView.requestFocus();
194 }
195 }
196 });
Winson Chung83f59ab2015-05-05 17:21:58 -0700197 mHeaderView = findViewById(R.id.header);
198 mHeaderView.setOnClickListener(this);
199 if (Utilities.isLmpOrAbove() && !DYNAMIC_HEADER_ELEVATION) {
200 mHeaderView.setElevation(DynamicGrid.pxFromDp(HEADER_ELEVATION_DP,
201 getContext().getResources().getDisplayMetrics()));
202 }
203 mSearchButtonView = mHeaderView.findViewById(R.id.search_button);
204 mSearchBarContainerView = findViewById(R.id.app_search_container);
205 mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button);
206 mDismissSearchButtonView.setOnClickListener(this);
Winson Chungde34aa42015-05-07 18:21:28 -0700207 mSearchBarEditView = (AppsContainerSearchEditTextView) findViewById(R.id.app_search_box);
Winson Chung83f59ab2015-05-05 17:21:58 -0700208 if (mSearchBarEditView != null) {
209 mSearchBarEditView.addTextChangedListener(this);
210 mSearchBarEditView.setOnEditorActionListener(this);
Winson Chungde34aa42015-05-07 18:21:28 -0700211 if (DISMISS_SEARCH_ON_BACK) {
212 mSearchBarEditView.setOnBackKeyListener(
213 new AppsContainerSearchEditTextView.OnBackKeyListener() {
214 @Override
215 public void onBackKey() {
216 hideSearchField(true, true);
217 }
218 });
219 }
Winson Chung0f785722015-04-08 10:27:49 -0700220 }
Winson Chung75cc8252015-04-10 10:19:58 -0700221 mAppsRecyclerView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view);
222 mAppsRecyclerView.setApps(mApps);
223 mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow);
224 mAppsRecyclerView.setLayoutManager(mLayoutManager);
225 mAppsRecyclerView.setAdapter(mAdapter);
226 mAppsRecyclerView.setHasFixedSize(true);
Winson Chung83f59ab2015-05-05 17:21:58 -0700227 mAppsRecyclerView.setOnScrollListenerProxy(new RecyclerView.OnScrollListener() {
228 @Override
229 public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
230 // Do nothing
231 }
232
233 @Override
234 public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
235 mRecyclerViewScrollY += dy;
236 onRecyclerViewScrolled();
237 }
238 });
Winson Chung93f98ea2015-03-10 16:28:47 -0700239 if (mItemDecoration != null) {
Winson Chung75cc8252015-04-10 10:19:58 -0700240 mAppsRecyclerView.addItemDecoration(mItemDecoration);
Winson Chung93f98ea2015-03-10 16:28:47 -0700241 }
Winson Chung94804152015-05-08 13:06:44 -0700242 onUpdateBackgrounds();
243 onUpdatePaddings();
Winson Chungb745afb2015-03-02 11:51:23 -0800244 }
245
246 @Override
Winson Chung94804152015-05-08 13:06:44 -0700247 protected void onFixedBoundsUpdated() {
248 // Update the number of items in the grid
249 LauncherAppState app = LauncherAppState.getInstance();
250 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
251 if (grid.updateAppsViewNumCols(getContext().getResources(), mFixedBounds.width())) {
252 mNumAppsPerRow = grid.appsViewNumCols;
253 mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow);
254 mAdapter.setNumAppsPerRow(mNumAppsPerRow);
255 mApps.setNumAppsPerRow(mNumAppsPerRow);
256 }
Winson Chung0f785722015-04-08 10:27:49 -0700257 }
258
259 /**
Winson Chung94804152015-05-08 13:06:44 -0700260 * Update the padding of the Apps view and children. To ensure that the RecyclerView has the
261 * full width to handle touches right to the edge of the screen, we only apply the top and
262 * bottom padding to the AppsContainerView and then the left/right padding on the RecyclerView
263 * itself. In particular, the left/right padding is applied to the background of the view,
264 * and then additionally inset by the start margin.
Winson Chung0f785722015-04-08 10:27:49 -0700265 */
Winson Chung94804152015-05-08 13:06:44 -0700266 @Override
267 protected void onUpdatePaddings() {
268 boolean isRtl = (getResources().getConfiguration().getLayoutDirection() ==
269 LAYOUT_DIRECTION_RTL);
270 boolean hasSearchBar = (mSearchBarEditView != null) &&
271 (mSearchBarEditView.getVisibility() == View.VISIBLE);
Winson Chung0f785722015-04-08 10:27:49 -0700272
Winson Chung94804152015-05-08 13:06:44 -0700273 if (mFixedBounds.isEmpty()) {
274 // If there are no fixed bounds, then use the default padding and insets
275 setPadding(mInsets.left, mContainerInset + mInsets.top, mInsets.right,
276 mContainerInset + mInsets.bottom);
277 } else {
278 // If there are fixed bounds, then we update the padding to reflect the fixed bounds.
279 setPadding(mFixedBounds.left, mFixedBounds.top, getMeasuredWidth() - mFixedBounds.right,
280 mInsets.bottom);
Winson Chung0f785722015-04-08 10:27:49 -0700281 }
Winson Chung94804152015-05-08 13:06:44 -0700282
283 // Update the apps recycler view, inset it by the container inset as well
284 int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
285 if (isRtl) {
286 mAppsRecyclerView.setPadding(inset, inset, inset + mContentMarginStart, inset);
287 } else {
288 mAppsRecyclerView.setPadding(inset + mContentMarginStart, inset, inset, inset);
289 }
290
291 // Update the header bar
292 if (hasSearchBar) {
293 LinearLayout.LayoutParams lp =
294 (LinearLayout.LayoutParams) mHeaderView.getLayoutParams();
295 lp.leftMargin = lp.rightMargin = inset;
296 }
297 }
298
299 /**
300 * Update the background of the Apps view and children.
301 */
302 @Override
303 protected void onUpdateBackgrounds() {
304 int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset;
305 boolean hasSearchBar = (mSearchBarEditView != null) &&
306 (mSearchBarEditView.getVisibility() == View.VISIBLE);
307
308 // Update the background of the reveal view and list to be inset with the fixed bound
309 // insets instead of the default insets
310 mAppsRecyclerView.setBackground(new InsetDrawable(
311 getContext().getResources().getDrawable(
312 hasSearchBar ? R.drawable.apps_list_search_bg : R.drawable.apps_list_bg),
313 inset, 0, inset, 0));
314 getRevealView().setBackground(new InsetDrawable(
315 getContext().getResources().getDrawable(R.drawable.apps_reveal_bg),
316 inset, 0, inset, 0));
Winson Chungb745afb2015-03-02 11:51:23 -0800317 }
318
319 @Override
Winson Chung466663e2015-04-17 12:11:01 -0700320 public boolean onInterceptTouchEvent(MotionEvent ev) {
321 return handleTouchEvent(ev);
322 }
323
324 @Override
325 public boolean onTouchEvent(MotionEvent ev) {
326 return handleTouchEvent(ev);
327 }
328
329 @Override
Winson Chungaa2ab252015-03-16 12:39:05 -0700330 public boolean onTouch(View v, MotionEvent ev) {
Winson Chung0f785722015-04-08 10:27:49 -0700331 switch (ev.getAction()) {
332 case MotionEvent.ACTION_DOWN:
333 case MotionEvent.ACTION_MOVE:
Winson Chung466663e2015-04-17 12:11:01 -0700334 mLastTouchPos.set((int) ev.getX(), (int) ev.getY());
Winson Chung0f785722015-04-08 10:27:49 -0700335 break;
Winson Chungb745afb2015-03-02 11:51:23 -0800336 }
337 return false;
338 }
339
340 @Override
Winson Chung83f59ab2015-05-05 17:21:58 -0700341 public void onClick(View v) {
342 if (v == mHeaderView) {
343 showSearchField();
344 } else if (v == mDismissSearchButtonView) {
345 hideSearchField(true, true);
346 }
347 }
348
349 @Override
Winson Chungb745afb2015-03-02 11:51:23 -0800350 public boolean onLongClick(View v) {
351 // Return early if this is not initiated from a touch
352 if (!v.isInTouchMode()) return false;
353 // When we have exited all apps or are in transition, disregard long clicks
354 if (!mLauncher.isAppsViewVisible() ||
355 mLauncher.getWorkspace().isSwitchingState()) return false;
356 // Return if global dragging is not enabled
357 if (!mLauncher.isDraggingEnabled()) return false;
358
359 // Start the drag
Winson Chung466663e2015-04-17 12:11:01 -0700360 mLauncher.getWorkspace().beginDragShared(v, mLastTouchPos, this, false);
Winson Chungcd99cd32015-04-29 11:03:24 -0700361 // Enter spring loaded mode
362 mLauncher.enterSpringLoadedDragMode();
Winson Chungb745afb2015-03-02 11:51:23 -0800363
364 return false;
365 }
366
367 @Override
368 public boolean supportsFlingToDelete() {
369 return true;
370 }
371
372 @Override
373 public boolean supportsAppInfoDropTarget() {
374 return true;
375 }
376
377 @Override
378 public boolean supportsDeleteDropTarget() {
Sunny Goyalfa401a12015-04-10 13:45:42 -0700379 return false;
Winson Chungb745afb2015-03-02 11:51:23 -0800380 }
381
382 @Override
383 public float getIntrinsicIconScaleFactor() {
384 LauncherAppState app = LauncherAppState.getInstance();
385 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
386 return (float) grid.allAppsIconSizePx / grid.iconSizePx;
387 }
388
389 @Override
390 public void onFlingToDeleteCompleted() {
391 // We just dismiss the drag when we fling, so cleanup here
392 mLauncher.exitSpringLoadedDragModeDelayed(true,
393 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
394 mLauncher.unlockScreenOrientation(false);
395 }
396
397 @Override
Winson Chung93f98ea2015-03-10 16:28:47 -0700398 public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
399 boolean success) {
Winson Chungb745afb2015-03-02 11:51:23 -0800400 if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() &&
401 !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
402 // Exit spring loaded mode if we have not successfully dropped or have not handled the
403 // drop in Workspace
404 mLauncher.exitSpringLoadedDragModeDelayed(true,
405 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
406 }
407 mLauncher.unlockScreenOrientation(false);
408
409 // Display an error message if the drag failed due to there not being enough space on the
410 // target layout we were dropping on.
411 if (!success) {
412 boolean showOutOfSpaceMessage = false;
413 if (target instanceof Workspace) {
414 int currentScreen = mLauncher.getCurrentWorkspaceScreen();
415 Workspace workspace = (Workspace) target;
416 CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen);
417 ItemInfo itemInfo = (ItemInfo) d.dragInfo;
418 if (layout != null) {
419 layout.calculateSpans(itemInfo);
420 showOutOfSpaceMessage =
421 !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY);
422 }
423 }
424 if (showOutOfSpaceMessage) {
425 mLauncher.showOutOfSpaceMessage(false);
426 }
427
428 d.deferDragViewCleanupPostAnimation = false;
429 }
430 }
Winson Chung93f98ea2015-03-10 16:28:47 -0700431
432 @Override
433 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
434 // Do nothing
435 }
436
437 @Override
438 public void onTextChanged(CharSequence s, int start, int before, int count) {
439 // Do nothing
440 }
441
442 @Override
443 public void afterTextChanged(final Editable s) {
Winson Chung82b016c2015-05-08 17:00:10 -0700444 String queryText = s.toString();
445 if (queryText.isEmpty()) {
Winson Chung93f98ea2015-03-10 16:28:47 -0700446 mApps.setFilter(null);
447 } else {
Winson Chung888b3a12015-03-13 11:14:16 -0700448 String formatStr = getResources().getString(R.string.apps_view_no_search_results);
Winson Chung82b016c2015-05-08 17:00:10 -0700449 mAdapter.setEmptySearchText(String.format(formatStr, queryText));
Winson Chung888b3a12015-03-13 11:14:16 -0700450
Winson Chung82b016c2015-05-08 17:00:10 -0700451 final String queryTextLower = queryText.toLowerCase();
Winson Chung93f98ea2015-03-10 16:28:47 -0700452 mApps.setFilter(new AlphabeticalAppsList.Filter() {
453 @Override
Winson Chung9121fbf2015-04-06 15:12:49 -0700454 public boolean retainApp(AppInfo info, String sectionName) {
Winson Chung82b016c2015-05-08 17:00:10 -0700455 if (sectionName.toLowerCase().contains(queryTextLower)) {
Winson Chung83f59ab2015-05-05 17:21:58 -0700456 return true;
457 }
Winson Chung82b016c2015-05-08 17:00:10 -0700458 String title = info.title.toString();
459 String[] words = SPLIT_PATTERN.split(title.toLowerCase());
Winson Chung83f59ab2015-05-05 17:21:58 -0700460 for (int i = 0; i < words.length; i++) {
Winson Chung82b016c2015-05-08 17:00:10 -0700461 if (words[i].startsWith(queryTextLower)) {
Winson Chung83f59ab2015-05-05 17:21:58 -0700462 return true;
463 }
464 }
465 return false;
Winson Chung93f98ea2015-03-10 16:28:47 -0700466 }
467 });
468 }
Winson Chung83f59ab2015-05-05 17:21:58 -0700469 scrollToTop();
Winson Chung93f98ea2015-03-10 16:28:47 -0700470 }
471
472 @Override
473 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
474 if (ALLOW_SINGLE_APP_LAUNCH && actionId == EditorInfo.IME_ACTION_DONE) {
Winson Chung9121fbf2015-04-06 15:12:49 -0700475 // Skip the quick-launch if there isn't exactly one item
476 if (mApps.getSize() != 1) {
477 return false;
Winson Chung93f98ea2015-03-10 16:28:47 -0700478 }
Winson Chung9121fbf2015-04-06 15:12:49 -0700479
480 List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
481 for (int i = 0; i < items.size(); i++) {
482 AlphabeticalAppsList.AdapterItem item = items.get(i);
483 if (!item.isSectionHeader) {
Winson Chung75cc8252015-04-10 10:19:58 -0700484 mAppsRecyclerView.getChildAt(i).performClick();
Winson Chung83f59ab2015-05-05 17:21:58 -0700485 getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
Winson Chung9121fbf2015-04-06 15:12:49 -0700486 return true;
487 }
488 }
Winson Chung93f98ea2015-03-10 16:28:47 -0700489 }
490 return false;
491 }
492
493 @Override
494 public View getContent() {
495 return null;
496 }
497
498 @Override
499 public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
Winson Chung75cc8252015-04-10 10:19:58 -0700500 // Do nothing
Winson Chung93f98ea2015-03-10 16:28:47 -0700501 }
502
503 @Override
504 public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
505 // Do nothing
506 }
507
508 @Override
509 public void onLauncherTransitionStep(Launcher l, float t) {
510 // Do nothing
511 }
512
513 @Override
514 public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
Winson Chung83f59ab2015-05-05 17:21:58 -0700515 if (mSearchBarEditView != null) {
Winson Chung0f785722015-04-08 10:27:49 -0700516 if (toWorkspace) {
Winson Chung83f59ab2015-05-05 17:21:58 -0700517 hideSearchField(false, false);
518 }
519 }
520 }
521
522 /**
523 * Updates the container when the recycler view is scrolled.
524 */
525 private void onRecyclerViewScrolled() {
526 if (DYNAMIC_HEADER_ELEVATION) {
527 int elevation = Math.min(mRecyclerViewScrollY, DynamicGrid.pxFromDp(HEADER_ELEVATION_DP,
528 getContext().getResources().getDisplayMetrics()));
529 if (Float.compare(mHeaderView.getElevation(), elevation) != 0) {
530 mHeaderView.setElevation(elevation);
Winson Chung0f785722015-04-08 10:27:49 -0700531 }
Winson Chung93f98ea2015-03-10 16:28:47 -0700532 }
533 }
Winson Chung0f785722015-04-08 10:27:49 -0700534
535 /**
Winson Chung466663e2015-04-17 12:11:01 -0700536 * Handles the touch events to dismiss all apps when clicking outside the bounds of the
537 * recycler view.
538 */
539 private boolean handleTouchEvent(MotionEvent ev) {
540 LauncherAppState app = LauncherAppState.getInstance();
541 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
542
543 switch (ev.getAction()) {
544 case MotionEvent.ACTION_DOWN:
545 if (!mFixedBounds.isEmpty()) {
546 // Outset the fixed bounds and check if the touch is outside all apps
547 Rect tmpRect = new Rect(mFixedBounds);
548 tmpRect.inset(-grid.allAppsIconSizePx / 2, 0);
549 if (ev.getX() < tmpRect.left || ev.getX() > tmpRect.right) {
550 mLastTouchDownPos.set((int) ev.getX(), (int) ev.getY());
551 return true;
552 }
553 } else {
554 // Check if the touch is outside all apps
555 if (ev.getX() < getPaddingLeft() ||
556 ev.getX() > (getWidth() - getPaddingRight())) {
557 mLastTouchDownPos.set((int) ev.getX(), (int) ev.getY());
558 return true;
559 }
560 }
561 break;
562 case MotionEvent.ACTION_UP:
563 if (mLastTouchDownPos.x > -1) {
564 ViewConfiguration viewConfig = ViewConfiguration.get(getContext());
565 float dx = ev.getX() - mLastTouchDownPos.x;
566 float dy = ev.getY() - mLastTouchDownPos.y;
567 float distance = (float) Math.hypot(dx, dy);
568 if (distance < viewConfig.getScaledTouchSlop()) {
569 // The background was clicked, so just go home
570 Launcher launcher = (Launcher) getContext();
571 launcher.showWorkspace(true);
572 return true;
573 }
574 }
575 // Fall through
576 case MotionEvent.ACTION_CANCEL:
577 mLastTouchDownPos.set(-1, -1);
578 break;
579 }
580 return false;
581 }
582
583 /**
Winson Chung83f59ab2015-05-05 17:21:58 -0700584 * Shows the search field.
585 */
586 private void showSearchField() {
587 // Show the search bar and focus the search
Winson Chungde34aa42015-05-07 18:21:28 -0700588 final int translationX = DynamicGrid.pxFromDp(SEARCH_TRANSLATION_X_DP,
589 getContext().getResources().getDisplayMetrics());
Winson Chung83f59ab2015-05-05 17:21:58 -0700590 mSearchBarContainerView.setVisibility(View.VISIBLE);
591 mSearchBarContainerView.setAlpha(0f);
Winson Chungde34aa42015-05-07 18:21:28 -0700592 mSearchBarContainerView.setTranslationX(translationX);
593 mSearchBarContainerView.animate()
594 .alpha(1f)
595 .translationX(0)
596 .setDuration(FADE_IN_DURATION)
597 .withLayer()
Winson Chung83f59ab2015-05-05 17:21:58 -0700598 .withEndAction(new Runnable() {
599 @Override
600 public void run() {
601 mSearchBarEditView.requestFocus();
602 getInputMethodManager().showSoftInput(mSearchBarEditView,
603 InputMethodManager.SHOW_IMPLICIT);
604 }
605 });
Winson Chungde34aa42015-05-07 18:21:28 -0700606 mSearchButtonView.animate()
607 .alpha(0f)
608 .translationX(-translationX)
609 .setDuration(FADE_OUT_DURATION)
610 .withLayer();
Winson Chung83f59ab2015-05-05 17:21:58 -0700611 }
612
613 /**
614 * Hides the search field.
615 */
616 private void hideSearchField(boolean animated, final boolean returnFocusToRecyclerView) {
Winson Chungde34aa42015-05-07 18:21:28 -0700617 final boolean resetTextField = mSearchBarEditView.getText().toString().length() > 0;
618 final int translationX = DynamicGrid.pxFromDp(SEARCH_TRANSLATION_X_DP,
619 getContext().getResources().getDisplayMetrics());
Winson Chung83f59ab2015-05-05 17:21:58 -0700620 if (animated) {
621 // Hide the search bar and focus the recycler view
Winson Chungde34aa42015-05-07 18:21:28 -0700622 mSearchBarContainerView.animate()
623 .alpha(0f)
624 .translationX(0)
625 .setDuration(FADE_IN_DURATION)
626 .withLayer()
Winson Chung83f59ab2015-05-05 17:21:58 -0700627 .withEndAction(new Runnable() {
628 @Override
629 public void run() {
630 mSearchBarContainerView.setVisibility(View.INVISIBLE);
Winson Chungde34aa42015-05-07 18:21:28 -0700631 if (resetTextField) {
632 mSearchBarEditView.setText("");
633 }
Winson Chung83f59ab2015-05-05 17:21:58 -0700634 mApps.setFilter(null);
635 if (returnFocusToRecyclerView) {
636 mAppsRecyclerView.requestFocus();
637 }
Winson Chung83f59ab2015-05-05 17:21:58 -0700638 }
639 });
Winson Chungde34aa42015-05-07 18:21:28 -0700640 mSearchButtonView.setTranslationX(-translationX);
641 mSearchButtonView.animate()
642 .alpha(1f)
643 .translationX(0)
644 .setDuration(FADE_OUT_DURATION)
645 .withLayer();
Winson Chung83f59ab2015-05-05 17:21:58 -0700646 } else {
647 mSearchBarContainerView.setVisibility(View.INVISIBLE);
Winson Chungde34aa42015-05-07 18:21:28 -0700648 if (resetTextField) {
649 mSearchBarEditView.setText("");
650 }
Winson Chung83f59ab2015-05-05 17:21:58 -0700651 mApps.setFilter(null);
652 mSearchButtonView.setAlpha(1f);
Winson Chungde34aa42015-05-07 18:21:28 -0700653 mSearchButtonView.setTranslationX(0f);
Winson Chung83f59ab2015-05-05 17:21:58 -0700654 if (returnFocusToRecyclerView) {
655 mAppsRecyclerView.requestFocus();
656 }
Winson Chung83f59ab2015-05-05 17:21:58 -0700657 }
658 getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0);
659 }
660
661 /**
662 * Returns an input method manager.
663 */
664 private InputMethodManager getInputMethodManager() {
665 return (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
666 }
Winson Chungb745afb2015-03-02 11:51:23 -0800667}