summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/AppsContainerView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/launcher3/AppsContainerView.java')
-rw-r--r--src/com/android/launcher3/AppsContainerView.java609
1 files changed, 609 insertions, 0 deletions
diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java
new file mode 100644
index 000000000..cabacec3c
--- /dev/null
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -0,0 +1,609 @@
+package com.android.launcher3;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.SectionIndexer;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * Represents a row in the apps list view.
+ */
+class AppsRow {
+ int sectionId;
+ String sectionDescription;
+ List<AppInfo> apps;
+
+ public AppsRow(int sId, String sc, List<AppInfo> ai) {
+ sectionId = sId;
+ sectionDescription = sc;
+ apps = ai;
+ }
+
+ public AppsRow(int sId, List<AppInfo> ai) {
+ sectionId = sId;
+ apps = ai;
+ }
+}
+
+/**
+ * An interface to an algorithm that generates app rows.
+ */
+interface AppRowAlgorithm {
+ public List<AppsRow> computeAppRows(List<AppInfo> sortedApps, int appsPerRow);
+ public int getIconViewLayoutId();
+ public int getRowViewLayoutId();
+ public void bindRowViewIconToInfo(BubbleTextView icon, AppInfo info);
+}
+
+/**
+ * Computes the rows in the apps list view.
+ */
+class SectionedAppsAlgorithm implements AppRowAlgorithm {
+
+ @Override
+ public List<AppsRow> computeAppRows(List<AppInfo> sortedApps, int appsPerRow) {
+ List<AppsRow> rows = new ArrayList<>();
+ LinkedHashMap<String, List<AppInfo>> sections = computeSectionedApps(sortedApps);
+ int sectionId = 0;
+ for (Map.Entry<String, List<AppInfo>> sectionEntry : sections.entrySet()) {
+ String section = sectionEntry.getKey();
+ List<AppInfo> apps = sectionEntry.getValue();
+ int numRows = (int) Math.ceil((float) apps.size() / appsPerRow);
+ for (int i = 0; i < numRows; i++) {
+ List<AppInfo> appsInRow = new ArrayList<>();
+ int offset = i * appsPerRow;
+ for (int j = 0; j < appsPerRow; j++) {
+ if (offset + j < apps.size()) {
+ appsInRow.add(apps.get(offset + j));
+ }
+ }
+ if (i == 0) {
+ rows.add(new AppsRow(sectionId, section, appsInRow));
+ } else {
+ rows.add(new AppsRow(sectionId, appsInRow));
+ }
+ }
+ sectionId++;
+ }
+ return rows;
+ }
+
+ @Override
+ public int getIconViewLayoutId() {
+ return R.layout.apps_grid_row_icon_view;
+ }
+
+ @Override
+ public int getRowViewLayoutId() {
+ return R.layout.apps_grid_row_view;
+ }
+
+ private LinkedHashMap<String, List<AppInfo>> computeSectionedApps(List<AppInfo> sortedApps) {
+ LinkedHashMap<String, List<AppInfo>> sections = new LinkedHashMap<>();
+ for (AppInfo info : sortedApps) {
+ String section = getSection(info);
+ List<AppInfo> sectionApps = sections.get(section);
+ if (sectionApps == null) {
+ sectionApps = new ArrayList<>();
+ sections.put(section, sectionApps);
+ }
+ sectionApps.add(info);
+ }
+ return sections;
+ }
+
+ @Override
+ public void bindRowViewIconToInfo(BubbleTextView icon, AppInfo info) {
+ icon.applyFromApplicationInfo(info);
+ }
+
+ private String getSection(AppInfo app) {
+ return app.title.toString().substring(0, 1).toLowerCase();
+ }
+}
+
+/**
+ * Computes the rows in the apps grid view.
+ */
+class ListedAppsAlgorithm implements AppRowAlgorithm {
+
+ @Override
+ public List<AppsRow> computeAppRows(List<AppInfo> sortedApps, int appsPerRow) {
+ List<AppsRow> rows = new ArrayList<>();
+ int sectionId = -1;
+ String prevSection = "";
+ for (AppInfo info : sortedApps) {
+ List<AppInfo> appsInRow = new ArrayList<>();
+ appsInRow.add(info);
+ String section = getSection(info);
+ if (!prevSection.equals(section)) {
+ prevSection = section;
+ sectionId++;
+ rows.add(new AppsRow(sectionId, section, appsInRow));
+ } else {
+ rows.add(new AppsRow(sectionId, appsInRow));
+ }
+ }
+ return rows;
+ }
+
+ @Override
+ public int getIconViewLayoutId() {
+ return R.layout.apps_list_row_icon_view;
+ }
+
+ @Override
+ public int getRowViewLayoutId() {
+ return R.layout.apps_list_row_view;
+ }
+
+ @Override
+ public void bindRowViewIconToInfo(BubbleTextView icon, AppInfo info) {
+ icon.applyFromApplicationInfo(info);
+ }
+
+ private String getSection(AppInfo app) {
+ return app.title.toString().substring(0, 1).toLowerCase();
+ }
+}
+
+/**
+ * The adapter of all the apps
+ */
+class AppsListAdapter extends BaseAdapter implements SectionIndexer {
+
+ private LayoutInflater mLayoutInflater;
+ private List<AppsRow> mAppRows = new ArrayList<>();
+ private View.OnTouchListener mTouchListener;
+ private View.OnClickListener mIconClickListener;
+ private View.OnLongClickListener mIconLongClickListener;
+ private AppRowAlgorithm mRowAlgorithm;
+ private int mAppsPerRow;
+
+ public AppsListAdapter(Context context, View.OnTouchListener touchListener,
+ View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener) {
+ mLayoutInflater = LayoutInflater.from(context);
+ mTouchListener = touchListener;
+ mIconClickListener = iconClickListener;
+ mIconLongClickListener = iconLongClickListener;
+ }
+
+ void setApps(List<AppsRow> apps, int appsPerRow, AppRowAlgorithm algo) {
+ mAppsPerRow = appsPerRow;
+ mRowAlgorithm = algo;
+ mAppRows.clear();
+ mAppRows.addAll(apps);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ return mAppRows.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mAppRows.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ AppsRow info = mAppRows.get(position);
+ ViewGroup row = (ViewGroup) convertView;
+ if (row == null) {
+ // Inflate the row and all the icon children necessary
+ row = (ViewGroup) mLayoutInflater.inflate(mRowAlgorithm.getRowViewLayoutId(),
+ parent, false);
+ for (int i = 0; i < mAppsPerRow; i++) {
+ BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
+ mRowAlgorithm.getIconViewLayoutId(), row, false);
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0,
+ ViewGroup.LayoutParams.WRAP_CONTENT, 1);
+ lp.gravity = Gravity.CENTER_VERTICAL;
+ icon.setLayoutParams(lp);
+ icon.setOnTouchListener(mTouchListener);
+ icon.setOnClickListener(mIconClickListener);
+ icon.setOnLongClickListener(mIconLongClickListener);
+ icon.setFocusable(true);
+ row.addView(icon);
+ }
+ }
+ // Bind the section header
+ TextView tv = (TextView) row.findViewById(R.id.section);
+ if (info.sectionDescription != null) {
+ tv.setText(info.sectionDescription);
+ tv.setVisibility(View.VISIBLE);
+ } else {
+ tv.setVisibility(View.INVISIBLE);
+ }
+ // Bind the icons
+ for (int i = 0; i < mAppsPerRow; i++) {
+ BubbleTextView icon = (BubbleTextView) row.getChildAt(i + 1);
+ if (i < info.apps.size()) {
+ mRowAlgorithm.bindRowViewIconToInfo(icon, info.apps.get(i));
+ icon.setVisibility(View.VISIBLE);
+ } else {
+ icon.setVisibility(View.INVISIBLE);
+ }
+ }
+ return row;
+ }
+
+ @Override
+ public Object[] getSections() {
+ ArrayList<Object> sections = new ArrayList<>();
+ int prevSectionId = -1;
+ for (AppsRow row : mAppRows) {
+ if (row.sectionId != prevSectionId) {
+ sections.add(row.sectionDescription.toUpperCase());
+ prevSectionId = row.sectionId;
+ }
+ }
+ return sections.toArray();
+ }
+
+ @Override
+ public int getPositionForSection(int sectionIndex) {
+ for (int i = 0; i < mAppRows.size(); i++) {
+ AppsRow row = mAppRows.get(i);
+ if (row.sectionId == sectionIndex) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public int getSectionForPosition(int position) {
+ return mAppRows.get(position).sectionId;
+ }
+}
+
+/**
+ * The alphabetically sorted list of applications.
+ */
+class AlphabeticalAppList {
+
+ /**
+ * Callbacks for when this list is modified.
+ */
+ public interface Callbacks {
+ public void onAppsUpdated();
+ }
+
+ private List<AppInfo> mApps;
+ private Callbacks mCb;
+
+ public AlphabeticalAppList(Callbacks cb) {
+ mCb = cb;
+ }
+
+ /**
+ * Returns the list of applications.
+ */
+ public List<AppInfo> getApps() {
+ return mApps;
+ }
+
+ /**
+ * Sets the current set of apps.
+ */
+ public void setApps(List<AppInfo> apps) {
+ Collections.sort(apps, LauncherModel.getAppNameComparator());
+ mApps = apps;
+ mCb.onAppsUpdated();
+ }
+
+ /**
+ * Adds new apps to the list.
+ */
+ public void addApps(List<AppInfo> apps) {
+ // We add it in place, in alphabetical order
+ Comparator<AppInfo> appNameComparator = LauncherModel.getAppNameComparator();
+ for (AppInfo info : apps) {
+ // This call will return the exact index of where the item is if >= 0, or the index
+ // where it should be inserted if < 0.
+ int index = Collections.binarySearch(mApps, info, appNameComparator);
+ if (index < 0) {
+ mApps.add(-(index + 1), info);
+ }
+ }
+ mCb.onAppsUpdated();
+ }
+
+ /**
+ * Updates existing apps in the list
+ */
+ public void updateApps(List<AppInfo> apps) {
+ Comparator<AppInfo> appNameComparator = LauncherModel.getAppNameComparator();
+ for (AppInfo info : apps) {
+ int index = mApps.indexOf(info);
+ if (index != -1) {
+ mApps.set(index, info);
+ } else {
+ index = Collections.binarySearch(mApps, info, appNameComparator);
+ if (index < 0) {
+ mApps.add(-(index + 1), info);
+ }
+ }
+ }
+ mCb.onAppsUpdated();
+ }
+
+ /**
+ * Removes some apps from the list.
+ */
+ public void removeApps(List<AppInfo> apps) {
+ for (AppInfo info : apps) {
+ int removeIndex = findAppByComponent(mApps, info);
+ if (removeIndex != -1) {
+ mApps.remove(removeIndex);
+ }
+ }
+ mCb.onAppsUpdated();
+ }
+
+ /**
+ * Finds the index of an app given a target AppInfo.
+ */
+ private int findAppByComponent(List<AppInfo> apps, AppInfo targetInfo) {
+ ComponentName targetComponent = targetInfo.intent.getComponent();
+ int length = apps.size();
+ for (int i = 0; i < length; ++i) {
+ AppInfo info = apps.get(i);
+ if (info.user.equals(info.user)
+ && info.intent.getComponent().equals(targetComponent)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+}
+
+/**
+ * The all apps list view container.
+ */
+public class AppsContainerView extends FrameLayout implements DragSource, View.OnTouchListener,
+ View.OnLongClickListener, Insettable, AlphabeticalAppList.Callbacks {
+
+ static final int GRID_LAYOUT = 0;
+ static final int LIST_LAYOUT = 1;
+ static final int USE_LAYOUT = LIST_LAYOUT;
+
+ private Launcher mLauncher;
+ private AppRowAlgorithm mAppRowsAlgorithm;
+ private AppsListAdapter mAdapter;
+ private AlphabeticalAppList mApps;
+ private ListView mList;
+ private int mAppsRowSize;
+ private Point mLastTouchDownPos = new Point();
+ private Rect mPadding = new Rect();
+
+ 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) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AppsContainerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ LauncherAppState app = LauncherAppState.getInstance();
+ DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+ mLauncher = (Launcher) context;
+ if (USE_LAYOUT == GRID_LAYOUT) {
+ mAppRowsAlgorithm = new SectionedAppsAlgorithm();
+ mAppsRowSize = grid.allAppsRowsSize;
+ } else if (USE_LAYOUT == LIST_LAYOUT) {
+ mAppRowsAlgorithm = new ListedAppsAlgorithm();
+ mAppsRowSize = 1;
+ }
+ mAdapter = new AppsListAdapter(context, this, mLauncher, this);
+ mApps = new AlphabeticalAppList(this);
+ }
+
+ /**
+ * 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() {
+ mList.scrollTo(0, 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.all_apps_transition_overlay);
+ }
+
+ @Override
+ public void onAppsUpdated() {
+ List<AppsRow> rows = mAppRowsAlgorithm.computeAppRows(mApps.getApps(), mAppsRowSize);
+ mAdapter.setApps(rows, mAppsRowSize, mAppRowsAlgorithm);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mList = (ListView) findViewById(R.id.apps_list);
+ mList.setFastScrollEnabled(true);
+ mList.setFastScrollAlwaysVisible(true);
+ mList.setItemsCanFocus(true);
+ mList.setAdapter(mAdapter);
+ 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 event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN ||
+ event.getAction() == MotionEvent.ACTION_MOVE) {
+ mLastTouchDownPos.set((int) event.getX(), (int) event.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;
+ }
+ }
+}