• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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.picker;
18 
19 import static com.android.documentsui.util.FlagUtils.isUseMaterial3FlagEnabled;
20 
21 import android.content.Context;
22 import android.content.pm.PackageManager;
23 import android.graphics.drawable.Drawable;
24 import android.os.Bundle;
25 import android.text.Editable;
26 import android.text.TextUtils;
27 import android.text.TextWatcher;
28 import android.view.KeyEvent;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.Button;
33 import android.widget.EditText;
34 import android.widget.ImageView;
35 import android.widget.ProgressBar;
36 import android.widget.TextView;
37 
38 import androidx.fragment.app.Fragment;
39 import androidx.fragment.app.FragmentManager;
40 import androidx.fragment.app.FragmentTransaction;
41 
42 import com.android.documentsui.IconUtils;
43 import com.android.documentsui.Injector;
44 import com.android.documentsui.R;
45 import com.android.documentsui.base.BooleanConsumer;
46 import com.android.documentsui.base.DocumentInfo;
47 import com.android.documentsui.base.Shared;
48 
49 import com.google.android.material.button.MaterialButton;
50 import com.google.android.material.textfield.TextInputLayout;
51 
52 /**
53  * Display document title editor and save button.
54  */
55 public class SaveFragment extends Fragment {
56     public static final String TAG = "SaveFragment";
57 
58     private final BooleanConsumer mInProgressStateListener = this::setPending;
59 
60     private Injector<ActionHandler<PickActivity>> mInjector;
61     private DocumentInfo mReplaceTarget;
62     private EditText mDisplayName;
63     private TextView mSave;
64     private MaterialButton mCancel;
65     private ProgressBar mProgress;
66     private boolean mIgnoreNextEdit;
67 
68     private static final String EXTRA_MIME_TYPE = "mime_type";
69     private static final String EXTRA_DISPLAY_NAME = "display_name";
70 
show(FragmentManager fm, String mimeType, String displayName)71     static void show(FragmentManager fm, String mimeType, String displayName) {
72         final Bundle args = new Bundle();
73         args.putString(EXTRA_MIME_TYPE, mimeType);
74         args.putString(EXTRA_DISPLAY_NAME, displayName);
75 
76         final SaveFragment fragment = new SaveFragment();
77         fragment.setArguments(args);
78 
79         final FragmentTransaction ft = fm.beginTransaction();
80         ft.replace(R.id.container_save, fragment, TAG);
81         ft.commitAllowingStateLoss();
82     }
83 
get(FragmentManager fm)84     public static SaveFragment get(FragmentManager fm) {
85         return (SaveFragment) fm.findFragmentByTag(TAG);
86     }
87 
88     @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)89     public View onCreateView(
90             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
91         final Context context = inflater.getContext();
92 
93         final View view = inflater.inflate(R.layout.fragment_save, container, false);
94 
95         final Drawable icon =
96                 IconUtils.loadMimeIcon(context, getArguments().getString(EXTRA_MIME_TYPE));
97         if (isUseMaterial3FlagEnabled()) {
98             final TextInputLayout titleWrapper =
99                     (TextInputLayout) view.findViewById(R.id.title_wrapper);
100             titleWrapper.setStartIconDrawable(icon);
101         } else {
102             final ImageView iconHolder = view.findViewById(android.R.id.icon);
103             iconHolder.setImageDrawable(icon);
104         }
105 
106         mDisplayName = (EditText) view.findViewById(android.R.id.title);
107         mDisplayName.addTextChangedListener(mDisplayNameWatcher);
108         mDisplayName.setText(getArguments().getString(EXTRA_DISPLAY_NAME));
109         mDisplayName.setOnKeyListener(
110                 new View.OnKeyListener() {
111                     @Override
112                     public boolean onKey(View v, int keyCode, KeyEvent event) {
113                         // Only handle key-down events. This is simpler, consistent with most other
114                         // UIs, and enables the handling of repeated key events from holding down a
115                         // key.
116                         if (event.getAction() != KeyEvent.ACTION_DOWN) {
117                             return false;
118                         }
119 
120                         // Returning false in this method will bubble the event up to
121                         // {@link BaseActivity#onKeyDown}. In order to prevent backspace popping
122                         // documents once the textView is empty, we are going to trap it here.
123                         if (keyCode == KeyEvent.KEYCODE_DEL
124                                 && TextUtils.isEmpty(mDisplayName.getText())) {
125                             return true;
126                         }
127 
128                         if (keyCode == KeyEvent.KEYCODE_ENTER && mSave.isEnabled()) {
129                             performSave();
130                             return true;
131                         }
132                         return false;
133                     }
134                 });
135 
136         mSave = (Button) view.findViewById(android.R.id.button1);
137         mSave.setOnClickListener(mSaveListener);
138         mSave.setEnabled(false);
139 
140         mCancel = (MaterialButton) view.findViewById(android.R.id.button2);
141         // For >600dp, this button is always available (via the values-600dp layout override).
142         // However on smaller layouts, the button is default GONE to save on space (the back gesture
143         // can cancel the saver) and when FEATURE_PC is set a cancel button is required due to the
144         // lack of a back gesture (mainly mouse support).
145         if (isUseMaterial3FlagEnabled()
146                 && mCancel != null
147                 && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PC)) {
148             mCancel.setOnClickListener(mCancelListener);
149             mCancel.setVisibility(View.VISIBLE);
150             mCancel.setEnabled(true);
151         }
152 
153         mProgress = (ProgressBar) view.findViewById(android.R.id.progress);
154 
155         return view;
156     }
157 
158     @Override
onActivityCreated(Bundle savedInstanceState)159     public void onActivityCreated(Bundle savedInstanceState) {
160         super.onActivityCreated(savedInstanceState);
161         mInjector = ((PickActivity) getActivity()).getInjector();
162 
163         if (savedInstanceState != null) {
164             mReplaceTarget = savedInstanceState.getParcelable(Shared.EXTRA_DOC);
165         }
166     }
167 
168     @Override
onSaveInstanceState(Bundle outBundle)169     public void onSaveInstanceState(Bundle outBundle) {
170         super.onSaveInstanceState(outBundle);
171 
172         outBundle.putParcelable(Shared.EXTRA_DOC, mReplaceTarget);
173     }
174 
175     private TextWatcher mDisplayNameWatcher = new TextWatcher() {
176         @Override
177         public void onTextChanged(CharSequence s, int start, int before, int count) {
178             if (mIgnoreNextEdit) {
179                 mIgnoreNextEdit = false;
180             } else {
181                 mReplaceTarget = null;
182             }
183         }
184 
185         @Override
186         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
187             // ignored
188         }
189 
190         @Override
191         public void afterTextChanged(Editable s) {
192             // ignored
193         }
194     };
195 
196     private View.OnClickListener mSaveListener = new View.OnClickListener() {
197         @Override
198         public void onClick(View v) {
199             performSave();
200         }
201 
202     };
203 
204     private View.OnClickListener mCancelListener = new View.OnClickListener() {
205         @Override
206         public void onClick(View v) {
207             mInjector.actions.finishPicking();
208         }
209     };
210 
performSave()211     private void performSave() {
212         if (mReplaceTarget != null) {
213             mInjector.actions.saveDocument(getChildFragmentManager(), mReplaceTarget);
214         } else {
215             final String mimeType = getArguments().getString(EXTRA_MIME_TYPE);
216             final String displayName = mDisplayName.getText().toString();
217             mInjector.actions.saveDocument(mimeType, displayName, mInProgressStateListener);
218         }
219     }
220 
221     /**
222      * Set given document as target for in-place writing if user hits save
223      * without changing the filename. Can be set to {@code null} if user
224      * navigates outside the target directory.
225      */
setReplaceTarget(DocumentInfo replaceTarget)226     public void setReplaceTarget(DocumentInfo replaceTarget) {
227         mReplaceTarget = replaceTarget;
228 
229         if (mReplaceTarget != null) {
230             getArguments().putString(EXTRA_DISPLAY_NAME, replaceTarget.displayName);
231             mIgnoreNextEdit = true;
232             mDisplayName.setText(replaceTarget.displayName);
233         }
234     }
235 
prepareForDirectory(DocumentInfo cwd)236     public void prepareForDirectory(DocumentInfo cwd) {
237         setSaveEnabled(cwd != null && cwd.isCreateSupported());
238     }
239 
setSaveEnabled(boolean enabled)240     private void setSaveEnabled(boolean enabled) {
241         mSave.setEnabled(enabled);
242     }
243 
setPending(boolean pending)244     private void setPending(boolean pending) {
245         mSave.setVisibility(pending ? View.INVISIBLE : View.VISIBLE);
246         mProgress.setVisibility(pending ? View.VISIBLE : View.GONE);
247     }
248 }
249