• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.documentsui.dirlist;
18 
19 import static com.android.documentsui.base.SharedMinimal.TAG;
20 
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.app.DialogFragment;
24 import android.app.FragmentManager;
25 import android.content.ContentProviderClient;
26 import android.content.ContentResolver;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.DialogInterface.OnClickListener;
30 import android.net.Uri;
31 import android.os.AsyncTask;
32 import android.os.Bundle;
33 import android.provider.DocumentsContract;
34 import android.support.annotation.Nullable;
35 import android.support.design.widget.Snackbar;
36 import android.support.design.widget.TextInputLayout;
37 import android.util.Log;
38 import android.view.KeyEvent;
39 import android.view.LayoutInflater;
40 import android.view.View;
41 import android.view.inputmethod.EditorInfo;
42 import android.widget.Button;
43 import android.widget.EditText;
44 import android.widget.TextView;
45 import android.widget.TextView.OnEditorActionListener;
46 
47 import com.android.documentsui.BaseActivity;
48 import com.android.documentsui.DocumentsApplication;
49 import com.android.documentsui.Metrics;
50 import com.android.documentsui.R;
51 import com.android.documentsui.base.DocumentInfo;
52 import com.android.documentsui.base.Shared;
53 import com.android.documentsui.ui.Snackbars;
54 
55 /**
56  * Dialog to rename file or directory.
57  */
58 public class RenameDocumentFragment extends DialogFragment {
59     private static final String TAG_RENAME_DOCUMENT = "rename_document";
60     private DocumentInfo mDocument;
61     private EditText mEditText;
62     private TextInputLayout mRenameInputWrapper;
63     private @Nullable DialogInterface mDialog;
64 
show(FragmentManager fm, DocumentInfo document)65     public static void show(FragmentManager fm, DocumentInfo document) {
66         final RenameDocumentFragment dialog = new RenameDocumentFragment();
67         dialog.mDocument = document;
68         dialog.show(fm, TAG_RENAME_DOCUMENT);
69     }
70 
71     /**
72      * Creates the dialog UI.
73      * @param savedInstanceState
74      * @return
75      */
76     @Override
onCreateDialog(Bundle savedInstanceState)77     public Dialog onCreateDialog(Bundle savedInstanceState) {
78         Context context = getActivity();
79         AlertDialog.Builder builder = new AlertDialog.Builder(context);
80         LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
81         View view = dialogInflater.inflate(R.layout.dialog_file_name, null, false);
82 
83         mEditText = (EditText) view.findViewById(android.R.id.text1);
84         mRenameInputWrapper = (TextInputLayout) view.findViewById(R.id.rename_input_wrapper);
85         builder.setTitle(R.string.menu_rename);
86         builder.setView(view);
87         builder.setPositiveButton(android.R.string.ok, null);
88         builder.setNegativeButton(android.R.string.cancel, null);
89 
90         final AlertDialog dialog = builder.create();
91 
92         dialog.setOnShowListener(this::onShowDialog);
93 
94         // Workaround for the problem - virtual keyboard doesn't show on the phone.
95         Shared.ensureKeyboardPresent(context, dialog);
96 
97         mEditText.setOnEditorActionListener(
98                 new OnEditorActionListener() {
99                     @Override
100                     public boolean onEditorAction(
101                             TextView view, int actionId, @Nullable KeyEvent event) {
102                         if ((actionId == EditorInfo.IME_ACTION_DONE) || (event != null
103                                 && event.getKeyCode() == KeyEvent.KEYCODE_ENTER
104                                 && event.hasNoModifiers())) {
105                             renameDocuments(mEditText.getText().toString());
106                         }
107                         return false;
108                     }
109                 });
110         mEditText.requestFocus();
111         return dialog;
112     }
113 
onShowDialog(DialogInterface dialog)114     private void onShowDialog(DialogInterface dialog){
115         mDialog = dialog;
116         Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
117         button.setOnClickListener(this::onClickDialog);
118     }
119 
onClickDialog(View view)120     private void onClickDialog(View view) {
121         renameDocuments(mEditText.getText().toString());
122     }
123 
124     /**
125      * Sets/Restores the data.
126      * @param savedInstanceState
127      * @return
128      */
129     @Override
onActivityCreated(Bundle savedInstanceState)130     public void onActivityCreated(Bundle savedInstanceState) {
131         super.onActivityCreated(savedInstanceState);
132 
133         if(savedInstanceState == null) {
134             // Fragment created for the first time, we set the text.
135             // mDocument value was set in show
136             mEditText.setText(mDocument.displayName);
137         }
138         else {
139             // Fragment restored, text was restored automatically.
140             // mDocument value needs to be restored.
141             mDocument = savedInstanceState.getParcelable(Shared.EXTRA_DOC);
142         }
143         // Do selection in both cases, because we cleared it.
144         selectFileName(mEditText);
145     }
146 
147     @Override
onSaveInstanceState(Bundle outState)148     public void onSaveInstanceState(Bundle outState) {
149         // Clear selection before storing state and restore it manually,
150         // because otherwise after rotation selection is displayed with cut/copy menu visible :/
151         clearFileNameSelection(mEditText);
152 
153         super.onSaveInstanceState(outState);
154 
155         outState.putParcelable(Shared.EXTRA_DOC, mDocument);
156     }
157 
158     /**
159      * Validates if string is a proper document name.
160      * Checks if string is not empty. More rules might be added later.
161      * @param docName string representing document name
162      * @returns true if string is a valid name.
163      **/
isValidDocumentName(String docName)164     private boolean isValidDocumentName(String docName) {
165         return !docName.isEmpty();
166     }
167 
168     /**
169      * Fills text field with the file name and selects the name without extension.
170      *
171      * @param editText text field to be filled
172      */
selectFileName(EditText editText)173     private void selectFileName(EditText editText) {
174         String text = editText.getText().toString();
175         int separatorIndex = text.indexOf(".");
176         editText.setSelection(0,
177                 (separatorIndex == -1 || mDocument.isDirectory()) ? text.length() : separatorIndex);
178     }
179 
180     /**
181      * Clears selection in text field.
182      *
183      * @param editText text field to be cleared.
184      */
clearFileNameSelection(EditText editText)185     private void clearFileNameSelection(EditText editText) {
186         editText.setSelection(0, 0);
187     }
188 
renameDocuments(String newDisplayName)189     private void renameDocuments(String newDisplayName) {
190         BaseActivity activity = (BaseActivity) getActivity();
191 
192         if (newDisplayName.equals(mDocument.displayName)) {
193             mDialog.dismiss();
194         } else if (!isValidDocumentName(newDisplayName)) {
195             Log.w(TAG, "Failed to rename file - invalid name:" + newDisplayName);
196             Snackbars.makeSnackbar(getActivity(), R.string.rename_error,
197                     Snackbar.LENGTH_SHORT).show();
198         } else if (activity.getInjector().getModel().hasFileWithName(newDisplayName)){
199             mRenameInputWrapper.setError(getContext().getString(R.string.name_conflict));
200             selectFileName(mEditText);
201             Metrics.logRenameFileError(getContext());
202         } else {
203             new RenameDocumentsTask(activity, newDisplayName).execute(mDocument);
204         }
205 
206     }
207 
208     private class RenameDocumentsTask extends AsyncTask<DocumentInfo, Void, DocumentInfo> {
209         private final BaseActivity mActivity;
210         private final String mNewDisplayName;
211 
RenameDocumentsTask(BaseActivity activity, String newDisplayName)212         public RenameDocumentsTask(BaseActivity activity, String newDisplayName) {
213             mActivity = activity;
214             mNewDisplayName = newDisplayName;
215         }
216 
217         @Override
onPreExecute()218         protected void onPreExecute() {
219             mActivity.setPending(true);
220         }
221 
222         @Override
doInBackground(DocumentInfo... document)223         protected DocumentInfo doInBackground(DocumentInfo... document) {
224             assert(document.length == 1);
225 
226             return mActivity.getInjector().actions.renameDocument(mNewDisplayName, document[0]);
227         }
228 
229         @Override
onPostExecute(DocumentInfo result)230         protected void onPostExecute(DocumentInfo result) {
231             if (result != null) {
232                 Metrics.logRenameFileOperation(getContext());
233             } else {
234                 Snackbars.showRenameFailed(mActivity);
235                 Metrics.logRenameFileError(getContext());
236             }
237             if (mDialog != null) {
238                 mDialog.dismiss();
239             }
240             mActivity.setPending(false);
241         }
242     }
243 }
244