diff options
Diffstat (limited to 'src/com/android/launcher3/AppsContainerView.java')
-rw-r--r-- | src/com/android/launcher3/AppsContainerView.java | 609 |
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; + } + } +} |