Add more descriptive UI for conflicts while renaming.
Test: updated existing rename conflict test and ran all
Bug: 31647077
Change-Id: I013bb9348e4691aea42f41f58af83e18e8ca106d
diff --git a/res/layout/dialog_file_name.xml b/res/layout/dialog_file_name.xml
index 3a95a13..6f8f9cf 100644
--- a/res/layout/dialog_file_name.xml
+++ b/res/layout/dialog_file_name.xml
@@ -19,11 +19,16 @@
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:padding="?android:attr/listPreferredItemPaddingEnd">
-
- <EditText
- android:id="@android:id/text1"
+ <android.support.design.widget.TextInputLayout
+ android:id="@+id/rename_input_wrapper"
+ android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:inputType="text" />
-
+ android:layout_centerInParent="true" >
+ <EditText
+ android:id="@android:id/text1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="text"/>
+ </android.support.design.widget.TextInputLayout>
</FrameLayout>
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 0aaa12a..eae5187 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -817,7 +817,7 @@
// Model must be accessed in UI thread, since underlying cursor is not threadsafe.
List<DocumentInfo> docs = mModel.getDocuments(selected);
- RenameDocumentFragment.show(getFragmentManager(), docs.get(0));
+ RenameDocumentFragment.show(getFragmentManager(), docs.get(0), mModel::hasFileWithName);
}
private boolean isDocumentEnabled(String mimeType, int flags) {
diff --git a/src/com/android/documentsui/dirlist/Model.java b/src/com/android/documentsui/dirlist/Model.java
index 7a76098..9ba8b5c 100644
--- a/src/com/android/documentsui/dirlist/Model.java
+++ b/src/com/android/documentsui/dirlist/Model.java
@@ -45,8 +45,10 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Predicate;
/**
@@ -82,12 +84,14 @@
private static final String TAG = "Model";
+ /** Maps Model ID to cursor positions, for looking up items by Model ID. */
+ private final Map<String, Integer> mPositions = new HashMap<>();
+ private final Set<String> mFileNames = new HashSet<>();
+
private boolean mIsLoading;
private List<EventListener<Update>> mUpdateListeners = new ArrayList<>();
@Nullable private Cursor mCursor;
private int mCursorCount;
- /** Maps Model ID to cursor positions, for looking up items by Model ID. */
- private Map<String, Integer> mPositions = new HashMap<>();
private String mIds[] = new String[0];
@Nullable String info;
@@ -133,6 +137,7 @@
error = null;
doc = null;
mIsLoading = false;
+ mFileNames.clear();
notifyUpdateListeners();
}
@@ -174,7 +179,7 @@
*/
private void updateModelData() {
mIds = new String[mCursorCount];
-
+ mFileNames.clear();
mCursor.moveToPosition(-1);
for (int pos = 0; pos < mCursorCount; ++pos) {
if (!mCursor.moveToNext()) {
@@ -191,6 +196,7 @@
} else {
mIds[pos] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
}
+ mFileNames.add(getCursorString(mCursor, Document.COLUMN_DISPLAY_NAME));
}
// Populate the positions.
@@ -200,6 +206,10 @@
}
}
+ public boolean hasFileWithName(String name) {
+ return mFileNames.contains(name);
+ }
+
public @Nullable Cursor getItem(String modelId) {
Integer pos = mPositions.get(modelId);
if (pos == null) {
diff --git a/src/com/android/documentsui/dirlist/RenameDocumentFragment.java b/src/com/android/documentsui/dirlist/RenameDocumentFragment.java
index ec91fe6..e2b87ea 100644
--- a/src/com/android/documentsui/dirlist/RenameDocumentFragment.java
+++ b/src/com/android/documentsui/dirlist/RenameDocumentFragment.java
@@ -33,11 +33,13 @@
import android.provider.DocumentsContract;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
+import android.support.design.widget.TextInputLayout;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
@@ -50,6 +52,8 @@
import com.android.documentsui.base.Shared;
import com.android.documentsui.ui.Snackbars;
+import java.util.function.Predicate;
+
/**
* Dialog to rename file or directory.
*/
@@ -57,10 +61,15 @@
private static final String TAG_RENAME_DOCUMENT = "rename_document";
private DocumentInfo mDocument;
private EditText mEditText;
+ private TextInputLayout mRenameInputWrapper;
+ private Predicate<String> mHasFileNamed;
+ private @Nullable DialogInterface mDialog;
- public static void show(FragmentManager fm, DocumentInfo document) {
+ public static void show(
+ FragmentManager fm, DocumentInfo document, Predicate<String> hasFileNamed) {
final RenameDocumentFragment dialog = new RenameDocumentFragment();
dialog.mDocument = document;
+ dialog.mHasFileNamed = hasFileNamed;
dialog.show(fm, TAG_RENAME_DOCUMENT);
}
@@ -77,22 +86,16 @@
View view = dialogInflater.inflate(R.layout.dialog_file_name, null, false);
mEditText = (EditText) view.findViewById(android.R.id.text1);
+ mRenameInputWrapper = (TextInputLayout) view.findViewById(R.id.rename_input_wrapper);
builder.setTitle(R.string.menu_rename);
builder.setView(view);
-
- builder.setPositiveButton(
- android.R.string.ok,
- new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- renameDocuments(mEditText.getText().toString());
- }
- });
-
+ builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, null);
final AlertDialog dialog = builder.create();
+ dialog.setOnShowListener(this::onShowDialog);
+
// Workaround for the problem - virtual keyboard doesn't show on the phone.
Shared.ensureKeyboardPresent(context, dialog);
@@ -105,8 +108,6 @@
&& event.getKeyCode() == KeyEvent.KEYCODE_ENTER
&& event.hasNoModifiers())) {
renameDocuments(mEditText.getText().toString());
- dialog.dismiss();
- return true;
}
return false;
}
@@ -114,6 +115,16 @@
return dialog;
}
+ private void onShowDialog(DialogInterface dialog){
+ mDialog = dialog;
+ Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
+ button.setOnClickListener(this::onClickDialog);
+ }
+
+ private void onClickDialog(View view) {
+ renameDocuments(mEditText.getText().toString());
+ }
+
/**
* Sets/Restores the data.
* @param savedInstanceState
@@ -182,12 +193,16 @@
private void renameDocuments(String newDisplayName) {
BaseActivity activity = (BaseActivity) getActivity();
- if (isValidDocumentName(newDisplayName)) {
- new RenameDocumentsTask(activity, newDisplayName).execute(mDocument);
- } else {
+ if (!isValidDocumentName(newDisplayName)) {
Log.w(TAG, "Failed to rename file - invalid name:" + newDisplayName);
Snackbars.makeSnackbar(getActivity(), R.string.rename_error,
Snackbar.LENGTH_SHORT).show();
+ } else if (mHasFileNamed.test(newDisplayName)){
+ mRenameInputWrapper.setError(getContext().getString(R.string.name_conflict));
+ selectFileName(mEditText);
+ Metrics.logRenameFileError(getContext());
+ } else {
+ new RenameDocumentsTask(activity, newDisplayName).execute(mDocument);
}
}
@@ -234,6 +249,9 @@
Snackbars.showRenameFailed(mActivity);
Metrics.logRenameFileError(getContext());
}
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
mActivity.setPending(false);
}
}
diff --git a/tests/common/com/android/documentsui/bots/UiBot.java b/tests/common/com/android/documentsui/bots/UiBot.java
index d73dacf..82f4822 100644
--- a/tests/common/com/android/documentsui/bots/UiBot.java
+++ b/tests/common/com/android/documentsui/bots/UiBot.java
@@ -119,6 +119,11 @@
.perform(ViewActions.replaceText(text));
}
+ public void assertDialogText(String expected) throws UiObjectNotFoundException {
+ onView(TEXT_ENTRY)
+ .check(matches(withText(is(expected))));
+ }
+
public boolean inFixedLayout() {
TypedValue val = new TypedValue();
// We alias files_activity to either fixed or drawer layouts based
@@ -192,6 +197,20 @@
return title;
}
+ public UiObject findFileRenameDialog() {
+ UiSelector selector = new UiSelector().text("Rename");
+ UiObject title = mDevice.findObject(selector);
+ title.waitForExists(mTimeout);
+ return title;
+ }
+
+ public UiObject findRenameErrorMessage() {
+ UiSelector selector = new UiSelector().text(mContext.getString(R.string.name_conflict));
+ UiObject title = mDevice.findObject(selector);
+ title.waitForExists(mTimeout);
+ return title;
+ }
+
@SuppressWarnings("unchecked")
public void assertDialogOkButtonFocused() {
onView(withId(android.R.id.button1)).check(matches(hasFocus()));
diff --git a/tests/functional/com/android/documentsui/RenameDocumentUiTest.java b/tests/functional/com/android/documentsui/RenameDocumentUiTest.java
index 9848bd5..c761cad 100644
--- a/tests/functional/com/android/documentsui/RenameDocumentUiTest.java
+++ b/tests/functional/com/android/documentsui/RenameDocumentUiTest.java
@@ -138,20 +138,42 @@
}
public void testRename_NameExists() throws Exception {
+ renameWithConflict();
+
+ bots.main.clickDialogCancelButton();
+
+ bots.directory.assertDocumentsPresent(fileName1);
+ bots.directory.assertDocumentsPresent(fileName2);
+ bots.directory.assertDocumentsCount(4);
+ }
+
+ public void testRename_RecoverAfterConflict() throws Exception {
+ renameWithConflict();
+ device.waitForIdle();
+
+ bots.main.setDialogText(newName);
+
+ device.waitForIdle();
+ bots.main.clickDialogOkButton();
+
+ bots.directory.waitForDocument(newName);
+ bots.directory.assertDocumentsAbsent(fileName1);
+ bots.directory.assertDocumentsCount(4);
+ }
+
+ private void renameWithConflict() throws Exception {
// Check that document with the new name exists
bots.directory.assertDocumentsPresent(fileName2);
bots.directory.selectDocument(fileName1, 1);
clickRename();
- bots.main.setDialogText(fileName2);
-
+ bots.main.assertDialogText(fileName1);
+ assertFalse(bots.main.findRenameErrorMessage().exists());
bots.keyboard.pressEnter();
-
- bots.directory.assertSnackbar(R.string.rename_error);
- bots.directory.assertDocumentsPresent(fileName1);
- bots.directory.assertDocumentsPresent(fileName2);
- bots.directory.assertDocumentsCount(4);
+ assertTrue(bots.main.findRenameErrorMessage().exists());
+ bots.main.setDialogText(fileName2);
+ assertTrue(bots.main.findRenameErrorMessage().exists());
}
private void clickRename() throws UiObjectNotFoundException {