From ac56cff1860b71d3f164aedd268703936e08fdc0 Mon Sep 17 00:00:00 2001 From: Adam Cohen Date: Wed, 28 Sep 2011 20:45:37 -0700 Subject: Adding keyboard support to folders and fixing renaming rough edges Change-Id: I62e1a5699e4c7e8d53f5f7d6331a854270a83aa1 --- src/com/android/launcher2/FocusHelper.java | 161 ++++++++++++++++++++------ src/com/android/launcher2/Folder.java | 45 +++++-- src/com/android/launcher2/FolderEditText.java | 36 ++++++ src/com/android/launcher2/FolderInfo.java | 2 + src/com/android/launcher2/Hotseat.java | 2 +- src/com/android/launcher2/Launcher.java | 11 +- src/com/android/launcher2/Workspace.java | 2 +- 7 files changed, 212 insertions(+), 47 deletions(-) create mode 100644 src/com/android/launcher2/FolderEditText.java (limited to 'src/com') diff --git a/src/com/android/launcher2/FocusHelper.java b/src/com/android/launcher2/FocusHelper.java index f03073998..967b02ff4 100644 --- a/src/com/android/launcher2/FocusHelper.java +++ b/src/com/android/launcher2/FocusHelper.java @@ -23,6 +23,7 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.widget.TabHost; import android.widget.TabWidget; +import android.widget.TextView; import com.android.launcher.R; @@ -33,18 +34,25 @@ import java.util.Comparator; /** * A keyboard listener we set on all the workspace icons. */ -class BubbleTextViewKeyEventListener implements View.OnKeyListener { - @Override +class IconKeyEventListener implements View.OnKeyListener { public boolean onKey(View v, int keyCode, KeyEvent event) { - return FocusHelper.handleBubbleTextViewKeyEvent((BubbleTextView) v, keyCode, event); + return FocusHelper.handleIconKeyEvent(v, keyCode, event); + } +} + +/** + * A keyboard listener we set on all the workspace icons. + */ +class FolderKeyEventListener implements View.OnKeyListener { + public boolean onKey(View v, int keyCode, KeyEvent event) { + return FocusHelper.handleFolderKeyEvent(v, keyCode, event); } } /** * A keyboard listener we set on all the hotseat buttons. */ -class HotseatBubbleTextViewKeyEventListener implements View.OnKeyListener { - @Override +class HotseatIconKeyEventListener implements View.OnKeyListener { public boolean onKey(View v, int keyCode, KeyEvent event) { final Configuration configuration = v.getResources().getConfiguration(); return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation); @@ -56,7 +64,6 @@ class HotseatBubbleTextViewKeyEventListener implements View.OnKeyListener { * market icon and vice versa. */ class AppsCustomizeTabKeyEventListener implements View.OnKeyListener { - @Override public boolean onKey(View v, int keyCode, KeyEvent event) { return FocusHelper.handleAppsCustomizeTabKeyEvent(v, keyCode, event); } @@ -480,7 +487,6 @@ public class FocusHelper { final int buttonIndex = parent.indexOfChild(v); final int buttonCount = parent.getChildCount(); final int pageIndex = workspace.getCurrentPage(); - final int pageCount = workspace.getChildCount(); // NOTE: currently we don't special case for the phone UI in different // orientations, even though the hotseat is on the side in landscape mode. This @@ -517,7 +523,7 @@ public class FocusHelper { // Select the first bubble text view in the current page of the workspace final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex); final CellLayoutChildren children = layout.getChildrenLayout(); - final View newIcon = getBubbleTextViewInDirection(layout, children, -1, 1); + final View newIcon = getIconInDirection(layout, children, -1, 1); if (newIcon != null) { newIcon.requestFocus(); } else { @@ -569,38 +575,41 @@ public class FocusHelper { return views; } /** - * Private helper method to find the index of the next BubbleTextView in the delta direction. + * Private helper method to find the index of the next BubbleTextView or FolderIcon in the + * direction delta. + * * @param delta either -1 or 1 depending on the direction we want to search */ - private static View findIndexOfBubbleTextView(ArrayList views, int i, int delta) { + private static View findIndexOfIcon(ArrayList views, int i, int delta) { // Then we find the next BubbleTextView offset by delta from i final int count = views.size(); int newI = i + delta; while (0 <= newI && newI < count) { View newV = views.get(newI); - if (newV instanceof BubbleTextView) { + if (newV instanceof BubbleTextView || newV instanceof FolderIcon) { return newV; } newI += delta; } return null; } - private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, int i, + private static View getIconInDirection(CellLayout layout, ViewGroup parent, int i, int delta) { final ArrayList views = getCellLayoutChildrenSortedSpatially(layout, parent); - return findIndexOfBubbleTextView(views, i, delta); + return findIndexOfIcon(views, i, delta); } - private static View getBubbleTextViewInDirection(CellLayout layout, ViewGroup parent, View v, + private static View getIconInDirection(CellLayout layout, ViewGroup parent, View v, int delta) { final ArrayList views = getCellLayoutChildrenSortedSpatially(layout, parent); - return findIndexOfBubbleTextView(views, views.indexOf(v), delta); + return findIndexOfIcon(views, views.indexOf(v), delta); } /** - * Private helper method to find the next closest BubbleTextView in the delta direction on the - * next line. + * Private helper method to find the next closest BubbleTextView or FolderIcon in the direction + * delta on the next line. + * * @param delta either -1 or 1 depending on the line and direction we want to search */ - private static View getClosestBubbleTextViewOnLine(CellLayout layout, ViewGroup parent, View v, + private static View getClosestIconOnLine(CellLayout layout, ViewGroup parent, View v, int lineDelta) { final ArrayList views = getCellLayoutChildrenSortedSpatially(layout, parent); final CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); @@ -617,7 +626,8 @@ public class FocusHelper { View newV = views.get(index); CellLayout.LayoutParams tmpLp = (CellLayout.LayoutParams) newV.getLayoutParams(); boolean satisfiesRow = (lineDelta < 0) ? (tmpLp.cellY < row) : (tmpLp.cellY > row); - if (satisfiesRow && newV instanceof BubbleTextView) { + if (satisfiesRow && + (newV instanceof BubbleTextView || newV instanceof FolderIcon)) { float tmpDistance = (float) Math.sqrt(Math.pow(tmpLp.cellX - lp.cellX, 2) + Math.pow(tmpLp.cellY - lp.cellY, 2)); if (tmpDistance < closestDistance) { @@ -639,17 +649,15 @@ public class FocusHelper { } /** - * Handles key events in a Workspace containing BubbleTextView. + * Handles key events in a Workspace containing. */ - static boolean handleBubbleTextViewKeyEvent(BubbleTextView v, int keyCode, KeyEvent e) { + static boolean handleIconKeyEvent(View v, int keyCode, KeyEvent e) { CellLayoutChildren parent = (CellLayoutChildren) v.getParent(); final CellLayout layout = (CellLayout) parent.getParent(); final Workspace workspace = (Workspace) layout.getParent(); final ViewGroup launcher = (ViewGroup) workspace.getParent(); final ViewGroup tabs = (ViewGroup) launcher.findViewById(R.id.qsb_bar); final ViewGroup hotseat = (ViewGroup) launcher.findViewById(R.id.hotseat); - int iconIndex = parent.indexOfChild(v); - int iconCount = parent.getChildCount(); int pageIndex = workspace.indexOfChild(layout); int pageCount = workspace.getChildCount(); @@ -660,13 +668,13 @@ public class FocusHelper { case KeyEvent.KEYCODE_DPAD_LEFT: if (handleKeyEvent) { // Select the previous icon or the last icon on the previous page if possible - View newIcon = getBubbleTextViewInDirection(layout, parent, v, -1); + View newIcon = getIconInDirection(layout, parent, v, -1); if (newIcon != null) { newIcon.requestFocus(); } else { if (pageIndex > 0) { parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); - newIcon = getBubbleTextViewInDirection(layout, parent, + newIcon = getIconInDirection(layout, parent, parent.getChildCount(), -1); if (newIcon != null) { newIcon.requestFocus(); @@ -682,13 +690,13 @@ public class FocusHelper { case KeyEvent.KEYCODE_DPAD_RIGHT: if (handleKeyEvent) { // Select the next icon or the first icon on the next page if possible - View newIcon = getBubbleTextViewInDirection(layout, parent, v, 1); + View newIcon = getIconInDirection(layout, parent, v, 1); if (newIcon != null) { newIcon.requestFocus(); } else { if (pageIndex < (pageCount - 1)) { parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); - newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1); + newIcon = getIconInDirection(layout, parent, -1, 1); if (newIcon != null) { newIcon.requestFocus(); } else { @@ -703,7 +711,7 @@ public class FocusHelper { case KeyEvent.KEYCODE_DPAD_UP: if (handleKeyEvent) { // Select the closest icon in the previous line, otherwise select the tab bar - View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, -1); + View newIcon = getClosestIconOnLine(layout, parent, v, -1); if (newIcon != null) { newIcon.requestFocus(); wasHandled = true; @@ -715,7 +723,7 @@ public class FocusHelper { case KeyEvent.KEYCODE_DPAD_DOWN: if (handleKeyEvent) { // Select the closest icon in the next line, otherwise select the button bar - View newIcon = getClosestBubbleTextViewOnLine(layout, parent, v, 1); + View newIcon = getClosestIconOnLine(layout, parent, v, 1); if (newIcon != null) { newIcon.requestFocus(); wasHandled = true; @@ -730,7 +738,7 @@ public class FocusHelper { // if there is no previous page if (pageIndex > 0) { parent = getCellLayoutChildrenForIndex(workspace, pageIndex - 1); - View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1); + View newIcon = getIconInDirection(layout, parent, -1, 1); if (newIcon != null) { newIcon.requestFocus(); } else { @@ -738,7 +746,7 @@ public class FocusHelper { workspace.snapToPage(pageIndex - 1); } } else { - View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1); + View newIcon = getIconInDirection(layout, parent, -1, 1); if (newIcon != null) { newIcon.requestFocus(); } @@ -752,7 +760,7 @@ public class FocusHelper { // if there is no previous page if (pageIndex < (pageCount - 1)) { parent = getCellLayoutChildrenForIndex(workspace, pageIndex + 1); - View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1); + View newIcon = getIconInDirection(layout, parent, -1, 1); if (newIcon != null) { newIcon.requestFocus(); } else { @@ -760,7 +768,7 @@ public class FocusHelper { workspace.snapToPage(pageIndex + 1); } } else { - View newIcon = getBubbleTextViewInDirection(layout, parent, + View newIcon = getIconInDirection(layout, parent, parent.getChildCount(), -1); if (newIcon != null) { newIcon.requestFocus(); @@ -772,7 +780,90 @@ public class FocusHelper { case KeyEvent.KEYCODE_MOVE_HOME: if (handleKeyEvent) { // Select the first icon on this page - View newIcon = getBubbleTextViewInDirection(layout, parent, -1, 1); + View newIcon = getIconInDirection(layout, parent, -1, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_END: + if (handleKeyEvent) { + // Select the last icon on this page + View newIcon = getIconInDirection(layout, parent, + parent.getChildCount(), -1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + wasHandled = true; + break; + default: break; + } + return wasHandled; + } + + /** + * Handles key events for items in a Folder. + */ + static boolean handleFolderKeyEvent(View v, int keyCode, KeyEvent e) { + CellLayoutChildren parent = (CellLayoutChildren) v.getParent(); + final CellLayout layout = (CellLayout) parent.getParent(); + final Folder folder = (Folder) layout.getParent(); + View title = folder.mFolderName; + + final int action = e.getAction(); + final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP); + boolean wasHandled = false; + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (handleKeyEvent) { + // Select the previous icon + View newIcon = getIconInDirection(layout, parent, v, -1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (handleKeyEvent) { + // Select the next icon + View newIcon = getIconInDirection(layout, parent, v, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + title.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_UP: + if (handleKeyEvent) { + // Select the closest icon in the previous line + View newIcon = getClosestIconOnLine(layout, parent, v, -1); + if (newIcon != null) { + newIcon.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (handleKeyEvent) { + // Select the closest icon in the next line + View newIcon = getClosestIconOnLine(layout, parent, v, 1); + if (newIcon != null) { + newIcon.requestFocus(); + } else { + title.requestFocus(); + } + } + wasHandled = true; + break; + case KeyEvent.KEYCODE_MOVE_HOME: + if (handleKeyEvent) { + // Select the first icon on this page + View newIcon = getIconInDirection(layout, parent, -1, 1); if (newIcon != null) { newIcon.requestFocus(); } @@ -782,7 +873,7 @@ public class FocusHelper { case KeyEvent.KEYCODE_MOVE_END: if (handleKeyEvent) { // Select the last icon on this page - View newIcon = getBubbleTextViewInDirection(layout, parent, + View newIcon = getIconInDirection(layout, parent, parent.getChildCount(), -1); if (newIcon != null) { newIcon.requestFocus(); diff --git a/src/com/android/launcher2/Folder.java b/src/com/android/launcher2/Folder.java index 71b708128..1b863de14 100644 --- a/src/com/android/launcher2/Folder.java +++ b/src/com/android/launcher2/Folder.java @@ -93,7 +93,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList private int[] mEmptyCell = new int[2]; private Alarm mReorderAlarm = new Alarm(); private Alarm mOnExitAlarm = new Alarm(); - private TextView mFolderName; private int mFolderNameHeight; private Rect mHitRect = new Rect(); private Rect mTempRect = new Rect(); @@ -101,6 +100,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList private boolean mDeleteFolderOnDropCompleted = false; private boolean mSuppressFolderDeletion = false; private boolean mItemAddedBackToSelfViaIcon = false; + FolderEditText mFolderName; private boolean mIsEditingName = false; private InputMethodManager mInputMethodManager; @@ -134,8 +134,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList if (sHintText == null) { sHintText = res.getString(R.string.folder_hint_text); } - mLauncher = (Launcher) context; + // We need this view to be focusable in touch mode so that when text editing of the folder + // name is complete, we have something to focus on, thus hiding the cursor and giving + // reliable behvior when clicking the text field (since it will always gain focus on click). + setFocusableInTouchMode(true); } @Override @@ -143,7 +146,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList super.onFinishInflate(); mContent = (CellLayout) findViewById(R.id.folder_content); mContent.setGridSize(0, 0); - mFolderName = (TextView) findViewById(R.id.folder_name); + mFolderName = (FolderEditText) findViewById(R.id.folder_name); + mFolderName.setFolder(this); // We find out how tall the text view wants to be (it is set to wrap_content), so that // we can allocate the appropriate amount of space for it. @@ -153,7 +157,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // We disable action mode for now since it messes up the view on phones mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback); - mFolderName.setCursorVisible(false); mFolderName.setOnEditorActionListener(this); mFolderName.setSelectAllOnFocus(true); mFolderName.setInputType(mFolderName.getInputType() | @@ -233,7 +236,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList public void startEditingFolderName() { mFolderName.setHint(""); - mFolderName.setCursorVisible(true); mIsEditingName = true; } @@ -248,8 +250,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // gets saved. mInfo.setTitle(mFolderName.getText().toString()); LauncherModel.updateItemInDatabase(mLauncher, mInfo); - mFolderName.setCursorVisible(false); - mFolderName.clearFocus(); + + // In order to clear the focus from the text field, we set the focus on ourself. This + // ensures that every time the field is clicked, focus is gained, giving reliable behavior. + requestFocus(); + Selection.setSelection((Spannable) mFolderName.getText(), 0, 0); mIsEditingName = false; } @@ -320,6 +325,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } mItemsInvalidated = true; + updateTextViewFocus(); mInfo.addListener(this); if (!sDefaultFolderName.contentEquals(mInfo.title)) { @@ -410,12 +416,20 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList if (cling != null) { cling.bringToFront(); } + setFocusOnFirstChild(); } }); oa.setDuration(mExpandDuration); oa.start(); } + private void setFocusOnFirstChild() { + View firstChild = mContent.getChildAt(0, 0); + if (firstChild != null) { + firstChild.requestFocus(); + } + } + public void animateClosed() { if (!(getParent() instanceof DragLayer)) return; @@ -512,6 +526,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList CellLayout.LayoutParams lp = new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY); boolean insert = false; + textView.setOnKeyListener(new FolderKeyEventListener()); mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true); return true; } @@ -842,6 +857,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList parent.removeView(this); mDragController.removeDropTarget((DropTarget) this); clearFocus(); + mFolderIcon.requestFocus(); if (mRearrangeOnClose) { setupContentForNumItems(getItemCount()); @@ -888,6 +904,19 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } } + // This method keeps track of the last item in the folder for the purposes + // of keyboard focus + private void updateTextViewFocus() { + View lastChild = getItemAt(getItemCount() - 1); + getItemAt(getItemCount() - 1); + if (lastChild != null) { + mFolderName.setNextFocusDownId(lastChild.getId()); + mFolderName.setNextFocusRightId(lastChild.getId()); + mFolderName.setNextFocusLeftId(lastChild.getId()); + mFolderName.setNextFocusUpId(lastChild.getId()); + } + } + public void onDrop(DragObject d) { ShortcutInfo item; if (d.dragInfo instanceof ApplicationInfo) { @@ -963,7 +992,9 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } public void onItemsChanged() { + updateTextViewFocus(); } + public void onTitleChanged(CharSequence title) { } diff --git a/src/com/android/launcher2/FolderEditText.java b/src/com/android/launcher2/FolderEditText.java new file mode 100644 index 000000000..13169bd51 --- /dev/null +++ b/src/com/android/launcher2/FolderEditText.java @@ -0,0 +1,36 @@ +package com.android.launcher2; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.widget.EditText; + +public class FolderEditText extends EditText { + + private Folder mFolder; + + public FolderEditText(Context context) { + super(context); + } + + public FolderEditText(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FolderEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setFolder(Folder folder) { + mFolder = folder; + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + // Catch the back button on the soft keyboard so that we can just close the activity + if (event.getKeyCode() == android.view.KeyEvent.KEYCODE_BACK) { + mFolder.doneEditingFolderName(true); + } + return super.onKeyPreIme(keyCode, event); + } +} diff --git a/src/com/android/launcher2/FolderInfo.java b/src/com/android/launcher2/FolderInfo.java index 3ae31d278..f59707671 100644 --- a/src/com/android/launcher2/FolderInfo.java +++ b/src/com/android/launcher2/FolderInfo.java @@ -56,6 +56,7 @@ class FolderInfo extends ItemInfo { for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onAdd(item); } + itemsChanged(); } /** @@ -68,6 +69,7 @@ class FolderInfo extends ItemInfo { for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onRemove(item); } + itemsChanged(); } public void setTitle(CharSequence title) { diff --git a/src/com/android/launcher2/Hotseat.java b/src/com/android/launcher2/Hotseat.java index b9be6dca9..85412c669 100644 --- a/src/com/android/launcher2/Hotseat.java +++ b/src/com/android/launcher2/Hotseat.java @@ -58,7 +58,7 @@ public class Hotseat extends FrameLayout { public void setup(Launcher launcher) { mLauncher = launcher; - setOnKeyListener(new HotseatBubbleTextViewKeyEventListener()); + setOnKeyListener(new HotseatIconKeyEventListener()); } CellLayout getLayout() { diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java index 2997d6876..02fccc590 100644 --- a/src/com/android/launcher2/Launcher.java +++ b/src/com/android/launcher2/Launcher.java @@ -1147,16 +1147,18 @@ public final class Launcher extends Activity // also will cancel mWaitingForResult. closeSystemDialogs(); - closeFolder(); - boolean alreadyOnHome = ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); + Folder openFolder = mWorkspace.getOpenFolder(); // In all these cases, only animate if we're already on home mWorkspace.exitWidgetResizeMode(); - if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive()) { + if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() && + openFolder == null) { mWorkspace.moveToDefaultScreen(true); } + + closeFolder(); exitSpringLoadedDragMode(); showWorkspace(alreadyOnHome); @@ -1876,6 +1878,9 @@ public final class Launcher extends Activity public void closeFolder() { Folder folder = mWorkspace.getOpenFolder(); if (folder != null) { + if (folder.isEditingName()) { + folder.dismissEditingName(); + } closeFolder(folder); // Dismiss the folder cling diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java index bf18456ae..44c533b95 100644 --- a/src/com/android/launcher2/Workspace.java +++ b/src/com/android/launcher2/Workspace.java @@ -469,7 +469,7 @@ public class Workspace extends SmoothPagedView } layout = (CellLayout) getChildAt(screen); - child.setOnKeyListener(new BubbleTextViewKeyEventListener()); + child.setOnKeyListener(new IconKeyEventListener()); } CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); -- cgit v1.2.3