blob: 06fe93c1fe0eeb660ea1fdb5092bca33c2f87c9d [file] [log] [blame]
/*
* 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<AppInfo> apps) {
mApps.setApps(apps);
}
/**
* Adds new apps to the list.
*/
public void addApps(List<AppInfo> apps) {
mApps.addApps(apps);
}
/**
* Updates existing apps in the list
*/
public void updateApps(List<AppInfo> apps) {
mApps.updateApps(apps);
}
/**
* Removes some apps from the list.
*/
public void removeApps(List<AppInfo> 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<AppInfo> appsWithoutSections = mApps.getAppsWithoutSectionBreaks();
List<AppInfo> 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);
}
}
}