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