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