diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml index a1033f06ce..893d79654f 100644 --- a/res/layout/user_folder_icon_normalized.xml +++ b/res/layout/user_folder_icon_normalized.xml @@ -41,7 +41,7 @@ android:paddingLeft="12dp" android:paddingRight="12dp" > - suggestList) { - int cnt = Math.min(suggestList.size(), FolderNameProvider.SUGGEST_MAX); - CompletionInfo[] cInfo = new CompletionInfo[cnt]; - for (int i = 0; i < cnt; i++) { - cInfo[i] = new CompletionInfo(i, i, suggestList.get(i)); - } - post(() -> getContext().getSystemService(InputMethodManager.class) - .displayCompletions(this, cInfo)); - } - private boolean showSoftInput() { return requestFocus() && getContext().getSystemService(InputMethodManager.class) diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java index e2b7b68a97..0fea0dcc63 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -45,6 +45,8 @@ public class FolderInfo extends ItemInfo { */ public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004; + public static final int FLAG_MANUAL_FOLDER_NAME = 0x00000008; + public int options; /** @@ -140,4 +142,10 @@ public class FolderInfo extends ItemInfo { writer.updateItemInDatabase(this); } } + + @Override + protected String dumpProperties() { + return super.dumpProperties() + + " manuallyTypedTitle=" + hasOption(FLAG_MANUAL_FOLDER_NAME); + } } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 844189fc70..90d812548a 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -96,7 +96,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener, View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener { private static final String TAG = "Launcher.Folder"; - + private static final boolean DEBUG = false; /** * We avoid measuring {@link #mContent} with a 0 width or height, as this * results in CellLayout being measured as UNSPECIFIED, which it does not support. @@ -146,7 +146,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @Thunk FolderIcon mFolderIcon; @Thunk FolderPagedView mContent; - public ExtendedEditText mFolderName; + public FolderNameEditText mFolderName; private PageIndicatorDots mPageIndicator; protected View mFooter; @@ -306,6 +306,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mFolderName.setText(suggestedNames[0]); mFolderName.displayCompletions(Arrays.asList(suggestedNames).subList(1, suggestedNames.length)); + mFolderName.setEnteredCompose(false); } } mFolderName.setHint(""); @@ -318,7 +319,13 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // Convert to a string here to ensure that no other state associated with the text field // gets saved. String newTitle = mFolderName.getText().toString(); + if (DEBUG) { + Log.d(TAG, "onBackKey newTitle=" + newTitle); + } + mInfo.title = newTitle; + mInfo.setOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME, mFolderName.isEnteredCompose(), + mLauncher.getModelWriter()); mFolderIcon.onTitleChanged(newTitle); mLauncher.getModelWriter().updateItemInDatabase(mInfo); @@ -350,6 +357,10 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (DEBUG) { + Log.d(TAG, "onEditorAction actionId=" + actionId + " key=" + + (event != null ? event.getKeyCode() : "null event")); + } if (actionId == EditorInfo.IME_ACTION_DONE) { mFolderName.dispatchBackKey(); return true; @@ -436,7 +447,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo */ public void showSuggestedTitle(String[] suggestName) { if (FeatureFlags.FOLDER_NAME_SUGGEST.get() - && TextUtils.isEmpty(mFolderName.getText().toString())) { + && TextUtils.isEmpty(mFolderName.getText().toString()) + && !mInfo.hasOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME)) { if (suggestName.length > 0 && !TextUtils.isEmpty(suggestName[0])) { mFolderName.setHint(""); mFolderName.setText(suggestName[0]); @@ -552,9 +564,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo openFolder.close(true); } - if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) { - mLauncher.getFolderNameProvider().load(getContext()); - } mContent.bindItems(items); centerAboutIcon(); mItemsInvalidated = true; @@ -1495,6 +1504,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return ContainerType.FOLDER; } + /** + * Navigation bar back key or hardware input back key has been issued. + */ @Override public boolean onBackPressed() { if (isEditingName()) { diff --git a/src/com/android/launcher3/folder/FolderNameEditText.java b/src/com/android/launcher3/folder/FolderNameEditText.java new file mode 100644 index 0000000000..7e3f847052 --- /dev/null +++ b/src/com/android/launcher3/folder/FolderNameEditText.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2020 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.folder; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputConnectionWrapper; +import android.view.inputmethod.InputMethodManager; + +import com.android.launcher3.ExtendedEditText; + +import java.util.List; + +/** + * Handles additional edit text functionality to better support folder name suggestion. + * First, makes suggestion to the InputMethodManager via {@link #displayCompletions(List)} + * Second, intercepts whether user accepted the suggestion or manually edited their + * folder names. + */ +public class FolderNameEditText extends ExtendedEditText { + private static final String TAG = "FolderNameEditText"; + private static final boolean DEBUG = false; + + private boolean mEnteredCompose = false; + + public FolderNameEditText(Context context) { + super(context); + } + + public FolderNameEditText(Context context, AttributeSet attrs) { + // ctor chaining breaks the touch handling + super(context, attrs); + } + + public FolderNameEditText(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + InputConnection con = super.onCreateInputConnection(outAttrs); + FolderNameEditTextInputConnection connectionWrapper = + new FolderNameEditTextInputConnection(con, true); + return connectionWrapper; + } + + public void displayCompletions(List suggestList) { + int cnt = Math.min(suggestList.size(), FolderNameProvider.SUGGEST_MAX); + CompletionInfo[] cInfo = new CompletionInfo[cnt]; + for (int i = 0; i < cnt; i++) { + cInfo[i] = new CompletionInfo(i, i, suggestList.get(i)); + } + post(() -> getContext().getSystemService(InputMethodManager.class) + .displayCompletions(this, cInfo)); + } + + /** + * Within 's', the 'count' characters beginning at 'start' have just replaced + * old text 'before' + */ + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + String reason = "unknown"; + if (start == 0 && count == 0 && before > 0) { + reason = "suggestion was rejected"; + mEnteredCompose = true; + } + if (DEBUG) { + Log.d(TAG, "onTextChanged " + start + "," + before + "," + count + + ", " + reason); + } + } + + @Override + public void onCommitCompletion(CompletionInfo text) { + setText(text.getText()); + setSelection(text.getText().length()); + mEnteredCompose = false; + } + + protected void setEnteredCompose(boolean value) { + mEnteredCompose = value; + } + + protected boolean isEnteredCompose() { + if (DEBUG) { + Log.d(TAG, "isEnteredCompose " + mEnteredCompose); + } + return mEnteredCompose; + } + + private class FolderNameEditTextInputConnection extends InputConnectionWrapper { + + FolderNameEditTextInputConnection(InputConnection target, boolean mutable) { + super(target, mutable); + } + + @Override + public boolean setComposingText(CharSequence cs, int newCursorPos) { + mEnteredCompose = true; + return super.setComposingText(cs, newCursorPos); + } + } +} diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java index 782b0e2ce6..9ea292c6f8 100644 --- a/src/com/android/launcher3/folder/FolderNameProvider.java +++ b/src/com/android/launcher3/folder/FolderNameProvider.java @@ -109,11 +109,14 @@ public class FolderNameProvider { if (contains(candidatesOut, candidate)) { return; } + for (int i = 0; i < candidate.length(); i++) { if (TextUtils.isEmpty(candidatesOut[i])) { candidatesOut[i] = candidate; + return; } } + candidatesOut[candidate.length() - 1] = candidate; } private boolean contains(CharSequence[] list, CharSequence key) {