/* * 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; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.support.v7.widget.RecyclerView; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.TextView; import com.android.launcher3.util.Thunk; import java.util.List; /** * The all apps list view container. */ public class AppsContainerView extends FrameLayout implements DragSource, Insettable, TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener, View.OnLongClickListener { private static final boolean ALLOW_SINGLE_APP_LAUNCH = true; private static final int GRID_LAYOUT = 0; private static final int LIST_LAYOUT = 1; private static final int USE_LAYOUT = GRID_LAYOUT; @Thunk Launcher mLauncher; @Thunk AlphabeticalAppsList mApps; private RecyclerView.Adapter mAdapter; private RecyclerView.LayoutManager mLayoutManager; private RecyclerView.ItemDecoration mItemDecoration; @Thunk AppsContainerRecyclerView mAppsListView; private EditText mSearchBar; private int mNumAppsPerRow; private Point mLastTouchDownPos = new Point(); private Rect mPadding = new Rect(); private int mContentMarginStart; public AppsContainerView(Context context) { this(context, null); } public AppsContainerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public AppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); Resources res = context.getResources(); mLauncher = (Launcher) context; mApps = new AlphabeticalAppsList(context); if (USE_LAYOUT == GRID_LAYOUT) { mNumAppsPerRow = grid.appsViewNumCols; AppsGridAdapter adapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, mLauncher, this); adapter.setEmptySearchText(res.getString(R.string.loading_apps_message)); mLayoutManager = adapter.getLayoutManager(context); mItemDecoration = adapter.getItemDecoration(); mAdapter = adapter; mContentMarginStart = adapter.getContentMarginStart(); } else if (USE_LAYOUT == LIST_LAYOUT) { mNumAppsPerRow = 1; AppsListAdapter adapter = new AppsListAdapter(context, mApps, this, mLauncher, this); adapter.setEmptySearchText(res.getString(R.string.loading_apps_message)); mLayoutManager = adapter.getLayoutManager(context); mAdapter = adapter; } mApps.setAdapter(mAdapter); } /** * Sets the current set of apps. */ public void setApps(List apps) { mApps.setApps(apps); } /** * Adds new apps to the list. */ public void addApps(List apps) { mApps.addApps(apps); } /** * Updates existing apps in the list */ public void updateApps(List apps) { mApps.updateApps(apps); } /** * Removes some apps from the list. */ public void removeApps(List apps) { mApps.removeApps(apps); } /** * Scrolls this list view to the top. */ public void scrollToTop() { mAppsListView.scrollToPosition(0); } /** * Returns the content view used for the launcher transitions. */ public View getContentView() { return findViewById(R.id.apps_list); } /** * Returns the reveal view used for the launcher transitions. */ public View getRevealView() { return findViewById(R.id.apps_view_transition_overlay); } @Override protected void onFinishInflate() { boolean isRtl = (getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_RTL); if (USE_LAYOUT == GRID_LAYOUT) { ((AppsGridAdapter) mAdapter).setRtl(isRtl); } mSearchBar = (EditText) findViewById(R.id.app_search_box); mSearchBar.addTextChangedListener(this); mSearchBar.setOnEditorActionListener(this); mAppsListView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view); mAppsListView.setApps(mApps); mAppsListView.setNumAppsPerRow(mNumAppsPerRow); mAppsListView.setLayoutManager(mLayoutManager); mAppsListView.setAdapter(mAdapter); mAppsListView.setHasFixedSize(true); if (isRtl) { mAppsListView.setPadding( mAppsListView.getPaddingLeft(), mAppsListView.getPaddingTop(), mAppsListView.getPaddingRight() + mContentMarginStart, mAppsListView.getPaddingBottom()); } else { mAppsListView.setPadding( mAppsListView.getPaddingLeft() + mContentMarginStart, mAppsListView.getPaddingTop(), mAppsListView.getPaddingRight(), mAppsListView.getPaddingBottom()); } if (mItemDecoration != null) { mAppsListView.addItemDecoration(mItemDecoration); } mPadding.set(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom()); } @Override public void setInsets(Rect insets) { setPadding(mPadding.left + insets.left, mPadding.top + insets.top, mPadding.right + insets.right, mPadding.bottom + insets.bottom); } @Override public boolean onTouch(View v, MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_MOVE) { mLastTouchDownPos.set((int) ev.getX(), (int) ev.getY()); } return false; } @Override public boolean onLongClick(View v) { // Return early if this is not initiated from a touch if (!v.isInTouchMode()) return false; // When we have exited all apps or are in transition, disregard long clicks if (!mLauncher.isAppsViewVisible() || mLauncher.getWorkspace().isSwitchingState()) return false; // Return if global dragging is not enabled if (!mLauncher.isDraggingEnabled()) return false; // Start the drag mLauncher.getWorkspace().beginDragShared(v, mLastTouchDownPos, this, false); // We delay entering spring-loaded mode slightly to make sure the UI // thready is free of any work. postDelayed(new Runnable() { @Override public void run() { // We don't enter spring-loaded mode if the drag has been cancelled if (mLauncher.getDragController().isDragging()) { // Go into spring loaded mode (must happen before we startDrag()) mLauncher.enterSpringLoadedDragMode(); } } }, 150); return false; } @Override public boolean supportsFlingToDelete() { return true; } @Override public boolean supportsAppInfoDropTarget() { return true; } @Override public boolean supportsDeleteDropTarget() { return true; } @Override public float getIntrinsicIconScaleFactor() { LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); return (float) grid.allAppsIconSizePx / grid.iconSizePx; } @Override public void onFlingToDeleteCompleted() { // We just dismiss the drag when we fling, so cleanup here mLauncher.exitSpringLoadedDragModeDelayed(true, Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); mLauncher.unlockScreenOrientation(false); } @Override public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete, boolean success) { if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) { // Exit spring loaded mode if we have not successfully dropped or have not handled the // drop in Workspace mLauncher.exitSpringLoadedDragModeDelayed(true, Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null); } mLauncher.unlockScreenOrientation(false); // Display an error message if the drag failed due to there not being enough space on the // target layout we were dropping on. if (!success) { boolean showOutOfSpaceMessage = false; if (target instanceof Workspace) { int currentScreen = mLauncher.getCurrentWorkspaceScreen(); Workspace workspace = (Workspace) target; CellLayout layout = (CellLayout) workspace.getChildAt(currentScreen); ItemInfo itemInfo = (ItemInfo) d.dragInfo; if (layout != null) { layout.calculateSpans(itemInfo); showOutOfSpaceMessage = !layout.findCellForSpan(null, itemInfo.spanX, itemInfo.spanY); } } if (showOutOfSpaceMessage) { mLauncher.showOutOfSpaceMessage(false); } d.deferDragViewCleanupPostAnimation = false; } } @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) { if (s.toString().isEmpty()) { mApps.setFilter(null); } else { String formatStr = getResources().getString(R.string.apps_view_no_search_results); if (USE_LAYOUT == GRID_LAYOUT) { ((AppsGridAdapter) mAdapter).setEmptySearchText(String.format(formatStr, s.toString())); } else { ((AppsListAdapter) mAdapter).setEmptySearchText(String.format(formatStr, s.toString())); } final String filterText = s.toString().toLowerCase().replaceAll("\\s+", ""); mApps.setFilter(new AlphabeticalAppsList.Filter() { @Override public boolean retainApp(AppInfo info) { String title = info.title.toString(); String sectionName = mApps.getSectionNameForApp(info); return sectionName.toLowerCase().contains(filterText) || title.toLowerCase().replaceAll("\\s+", "").contains(filterText); } }); } } @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (ALLOW_SINGLE_APP_LAUNCH && actionId == EditorInfo.IME_ACTION_DONE) { List appsWithoutSections = mApps.getAppsWithoutSectionBreaks(); List apps = mApps.getApps(); if (appsWithoutSections.size() == 1) { mAppsListView.getChildAt(apps.indexOf(appsWithoutSections.get(0))).performClick(); InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(getWindowToken(), 0); } return true; } return false; } @Override public View getContent() { return null; } @Override public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { if (!toWorkspace) { // Disable the focus so that the search bar doesn't get focus mSearchBar.setFocusableInTouchMode(false); } } @Override public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { // Do nothing } @Override public void onLauncherTransitionStep(Launcher l, float t) { // Do nothing } @Override public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { if (toWorkspace) { // Clear the search bar mSearchBar.setText(""); } else { mSearchBar.setFocusableInTouchMode(true); } } }