• 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.Shared.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         return dialog;
111     }
112 
onShowDialog(DialogInterface dialog)113     private void onShowDialog(DialogInterface dialog){
114         mDialog = dialog;
115         Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
116         button.setOnClickListener(this::onClickDialog);
117     }
118 
onClickDialog(View view)119     private void onClickDialog(View view) {
120         renameDocuments(mEditText.getText().toString());
121     }
122 
123     /**
124      * Sets/Restores the data.
125      * @param savedInstanceState
126      * @return
127      */
128     @Override
onActivityCreated(Bundle savedInstanceState)129     public void onActivityCreated(Bundle savedInstanceState) {
130         super.onActivityCreated(savedInstanceState);
131 
132         if(savedInstanceState == null) {
133             // Fragment created for the first time, we set the text.
134             // mDocument value was set in show
135             mEditText.setText(mDocument.displayName);
136         }
137         else {
138             // Fragment restored, text was restored automatically.
139             // mDocument value needs to be restored.
140             mDocument = savedInstanceState.getParcelable(Shared.EXTRA_DOC);
141         }
142         // Do selection in both cases, because we cleared it.
143         selectFileName(mEditText);
144     }
145 
146     @Override
onSaveInstanceState(Bundle outState)147     public void onSaveInstanceState(Bundle outState) {
148         // Clear selection before storing state and restore it manually,
149         // because otherwise after rotation selection is displayed with cut/copy menu visible :/
150         clearFileNameSelection(mEditText);
151 
152         super.onSaveInstanceState(outState);
153 
154         outState.putParcelable(Shared.EXTRA_DOC, mDocument);
155     }
156 
157     /**
158      * Validates if string is a proper document name.
159      * Checks if string is not empty. More rules might be added later.
160      * @param docName string representing document name
161      * @returns true if string is a valid name.
162      **/
isValidDocumentName(String docName)163     private boolean isValidDocumentName(String docName) {
164         return !docName.isEmpty();
165     }
166 
167     /**
168      * Fills text field with the file name and selects the name without extension.
169      *
170      * @param editText text field to be filled
171      */
selectFileName(EditText editText)172     private void selectFileName(EditText editText) {
173         String text = editText.getText().toString();
174         int separatorIndex = text.indexOf(".");
175         editText.setSelection(0,
176                 (separatorIndex == -1 || mDocument.isDirectory()) ? text.length() : separatorIndex);
177     }
178 
179     /**
180      * Clears selection in text field.
181      *
182      * @param editText text field to be cleared.
183      */
clearFileNameSelection(EditText editText)184     private void clearFileNameSelection(EditText editText) {
185         editText.setSelection(0, 0);
186     }
187 
renameDocuments(String newDisplayName)188     private void renameDocuments(String newDisplayName) {
189         BaseActivity activity = (BaseActivity) getActivity();
190 
191         if (newDisplayName.equals(mDocument.displayName)) {
192             mDialog.dismiss();
193         } else if (!isValidDocumentName(newDisplayName)) {
194             Log.w(TAG, "Failed to rename file - invalid name:" + newDisplayName);
195             Snackbars.makeSnackbar(getActivity(), R.string.rename_error,
196                     Snackbar.LENGTH_SHORT).show();
197         } else if (activity.getInjector().getModel().hasFileWithName(newDisplayName)){
198             mRenameInputWrapper.setError(getContext().getString(R.string.name_conflict));
199             selectFileName(mEditText);
200             Metrics.logRenameFileError(getContext());
201         } else {
202             new RenameDocumentsTask(activity, newDisplayName).execute(mDocument);
203         }
204 
205     }
206 
207     private class RenameDocumentsTask extends AsyncTask<DocumentInfo, Void, DocumentInfo> {
208         private final BaseActivity mActivity;
209         private final String mNewDisplayName;
210 
RenameDocumentsTask(BaseActivity activity, String newDisplayName)211         public RenameDocumentsTask(BaseActivity activity, String newDisplayName) {
212             mActivity = activity;
213             mNewDisplayName = newDisplayName;
214         }
215 
216         @Override
onPreExecute()217         protected void onPreExecute() {
218             mActivity.setPending(true);
219         }
220 
221         @Override
doInBackground(DocumentInfo... document)222         protected DocumentInfo doInBackground(DocumentInfo... document) {
223             assert(document.length == 1);
224 
225             return mActivity.getInjector().actions.renameDocument(mNewDisplayName, document[0]);
226         }
227 
228         @Override
onPostExecute(DocumentInfo result)229         protected void onPostExecute(DocumentInfo result) {
230             if (result != null) {
231                 Metrics.logRenameFileOperation(getContext());
232             } else {
233                 Snackbars.showRenameFailed(mActivity);
234                 Metrics.logRenameFileError(getContext());
235             }
236             if (mDialog != null) {
237                 mDialog.dismiss();
238             }
239             mActivity.setPending(false);
240         }
241     }
242 }
243