Java tutorial
/* * Copyright (c) 2012 Hai Bison * * See the file LICENSE at the root directory of this project for copying * permission. */ package group.pals.android.lib.ui.filechooser; import group.pals.android.lib.ui.filechooser.FileChooserActivity.ViewType; import group.pals.android.lib.ui.filechooser.prefs.DisplayPrefs; import group.pals.android.lib.ui.filechooser.providers.BaseFileProviderUtils; import group.pals.android.lib.ui.filechooser.providers.DbUtils; import group.pals.android.lib.ui.filechooser.providers.ProviderUtils; import group.pals.android.lib.ui.filechooser.providers.basefile.BaseFileContract.BaseFile; import group.pals.android.lib.ui.filechooser.providers.history.HistoryContract; import group.pals.android.lib.ui.filechooser.providers.history.HistoryProviderUtils; import group.pals.android.lib.ui.filechooser.providers.localfile.LocalFileContract; import group.pals.android.lib.ui.filechooser.utils.E; import group.pals.android.lib.ui.filechooser.utils.EnvUtils; import group.pals.android.lib.ui.filechooser.utils.FileUtils; import group.pals.android.lib.ui.filechooser.utils.Texts; import group.pals.android.lib.ui.filechooser.utils.Utils; import group.pals.android.lib.ui.filechooser.utils.history.History; import group.pals.android.lib.ui.filechooser.utils.history.HistoryListener; import group.pals.android.lib.ui.filechooser.utils.history.HistoryStore; import group.pals.android.lib.ui.filechooser.utils.ui.Dlg; import group.pals.android.lib.ui.filechooser.utils.ui.LoadingDialog; import group.pals.android.lib.ui.filechooser.utils.ui.Ui; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.regex.Pattern; import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.ContentValues; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.database.DatabaseUtils; import android.graphics.Rect; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.text.format.DateUtils; import android.util.Log; import android.view.GestureDetector; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.Button; import android.widget.EditText; import android.widget.GridView; import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; /** * Fragment of files. * * @author Hai Bison * @since v5.4 beta */ public class FragmentFiles extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> { /** * The full name of this class. Generally used for debugging. */ private static final String CLASSNAME = FragmentFiles.class.getName(); /** * This key holds current location (a {@link Uri}), to restore it after * screen orientation changed. */ private static final String CURRENT_LOCATION = CLASSNAME + ".current_location"; /** * This key holds current history (a {@link History}{@code <}{@link Uri} * {@code >}), to restore it after screen orientation changed */ private static final String HISTORY = CLASSNAME + ".history"; private static final String PATH = CLASSNAME + ".path"; /** * All string extras. */ private static final String[] EXTRAS_STRING = { FileChooserActivity.EXTRA_DEFAULT_FILENAME, FileChooserActivity.EXTRA_DEFAULT_FILE_EXT, FileChooserActivity.EXTRA_FILE_PROVIDER_AUTHORITY, FileChooserActivity.EXTRA_NEGATIVE_REGEX_FILTER, FileChooserActivity.EXTRA_POSITIVE_REGEX_FILTER }; /** * All boolean extras. */ private static final String[] EXTRAS_BOOLEAN = { FileChooserActivity.EXTRA_DISPLAY_HIDDEN_FILES, FileChooserActivity.EXTRA_DOUBLE_TAP_TO_CHOOSE_FILES, FileChooserActivity.EXTRA_MULTI_SELECTION, FileChooserActivity.EXTRA_SAVE_DIALOG }; /** * All integer extras. */ private static final String[] EXTRAS_INTEGER = { FileChooserActivity.EXTRA_FILTER_MODE, FileChooserActivity.EXTRA_MAX_FILE_COUNT, FileChooserActivity.EXTRA_THEME }; /** * All parcelable extras. */ private static final String[] EXTRAS_PARCELABLE = { FileChooserActivity.EXTRA_ROOTPATH, FileChooserActivity.EXTRA_SELECT_FILE }; /** * Creates new instance. * * @param intent * the intent you got from {@link FileChooserActivity}. * @return the new instance of this fragment. */ public static FragmentFiles newInstance(Intent intent) { /* * Load the extras. */ final Bundle args = new Bundle(); for (String ex : EXTRAS_BOOLEAN) if (intent.hasExtra(ex)) args.putBoolean(ex, intent.getBooleanExtra(ex, false)); for (String ex : EXTRAS_INTEGER) if (intent.hasExtra(ex)) args.putInt(ex, intent.getIntExtra(ex, 0)); for (String ex : EXTRAS_PARCELABLE) if (intent.hasExtra(ex)) args.putParcelable(ex, intent.getParcelableExtra(ex)); for (String ex : EXTRAS_STRING) if (intent.hasExtra(ex)) args.putString(ex, intent.getStringExtra(ex)); return newInstance(args); }// newInstance() /** * Creates new instance. * * @param args * the arguments. * @return the new instance of this fragment. */ public static FragmentFiles newInstance(Bundle args) { FragmentFiles fragment = new FragmentFiles(); fragment.setArguments(args); return fragment; }// newInstance() // ==================== // "CONSTANT" VARIABLES /** * Task ID for loading directory content. */ private final int mIdLoaderData = EnvUtils.genId(); private String mFileProviderAuthority; private Uri mRoot; private int mFilterMode; private int mMaxFileCount; private boolean mIsMultiSelection; private boolean mIsSaveDialog; private boolean mDoubleTapToChooseFiles; private History<Uri> mHistory; private Uri mLastLocation; private Uri mCurrentLocation; private Handler mViewLoadingHandler = new Handler(); /** * The adapter of list view. */ private BaseFileAdapter mFileAdapter; private boolean mLoading = false; private boolean mNewLoader = true; /* * CONTROLS */ private View mBtnGoHome; private HorizontalScrollView mViewLocationsContainer; private ViewGroup mViewAddressBar; private View mViewGroupFiles; private ViewGroup mViewFilesContainer; private TextView mTextFullDirName; private AbsListView mViewFiles; private TextView mFooterView; private View mViewLoading; private Button mBtnOk; private EditText mTextSaveas; private ImageView mViewGoBack; private ImageView mViewGoForward; private GestureDetector mListviewFilesGestureDetector; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); /* * Load configurations. */ mFileProviderAuthority = getArguments().getString(FileChooserActivity.EXTRA_FILE_PROVIDER_AUTHORITY); if (mFileProviderAuthority == null) mFileProviderAuthority = LocalFileContract.getAuthority(getActivity()); mIsMultiSelection = getArguments().getBoolean(FileChooserActivity.EXTRA_MULTI_SELECTION); mIsSaveDialog = getArguments().getBoolean(FileChooserActivity.EXTRA_SAVE_DIALOG); if (mIsSaveDialog) mIsMultiSelection = false; mDoubleTapToChooseFiles = getArguments().getBoolean(FileChooserActivity.EXTRA_DOUBLE_TAP_TO_CHOOSE_FILES); mRoot = getArguments().getParcelable(FileChooserActivity.EXTRA_ROOTPATH); mFilterMode = getArguments().getInt(FileChooserActivity.EXTRA_FILTER_MODE, BaseFile.FILTER_FILES_ONLY); mMaxFileCount = getArguments().getInt(FileChooserActivity.EXTRA_MAX_FILE_COUNT, 1000); mFileAdapter = new BaseFileAdapter(getActivity(), mFilterMode, mIsMultiSelection); /* * History. */ if (savedInstanceState != null && savedInstanceState.get(HISTORY) instanceof HistoryStore<?>) mHistory = savedInstanceState.getParcelable(HISTORY); else mHistory = new HistoryStore<Uri>(); mHistory.addListener(new HistoryListener<Uri>() { @Override public void onChanged(History<Uri> history) { int idx = history.indexOf(getCurrentLocation()); mViewGoBack.setEnabled(idx > 0); mViewGoForward.setEnabled(idx >= 0 && idx < history.size() - 1); }// onChanged() }); }// onCreate() @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View rootView = inflater.inflate(R.layout.afc_fragment_files, container, false); /* * MAP CONTROLS */ mBtnGoHome = rootView.findViewById(R.id.afc_textview_home); mViewGoBack = (ImageView) rootView.findViewById(R.id.afc_button_go_back); mViewGoForward = (ImageView) rootView.findViewById(R.id.afc_button_go_forward); mViewAddressBar = (ViewGroup) rootView.findViewById(R.id.afc_view_locations); mViewLocationsContainer = (HorizontalScrollView) rootView.findViewById(R.id.afc_view_locations_container); mTextFullDirName = (TextView) rootView.findViewById(R.id.afc_textview_full_dir_name); mViewGroupFiles = rootView.findViewById(R.id.afc_viewgroup_files); mViewFilesContainer = (ViewGroup) rootView.findViewById(R.id.afc_view_files_container); mFooterView = (TextView) rootView.findViewById(R.id.afc_view_files_footer_view); mViewLoading = rootView.findViewById(R.id.afc_view_loading); mTextSaveas = (EditText) rootView.findViewById(R.id.afc_textview_saveas_filename); mBtnOk = (Button) rootView.findViewById(R.id.afc_button_ok); /* * INIT CONTROLS */ return rootView; }// onCreateView() @Override public void onActivityCreated(final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setupHeader(); setupViewFiles(); setupFooter(); initGestureDetector(); loadInitialPath(savedInstanceState); }// onActivityCreated() @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.afc_fragment_files, menu); }// onCreateOptionsMenu() @Override public void onPrepareOptionsMenu(Menu menu) { if (BuildConfig.DEBUG) Log.d(CLASSNAME, "onPrepareOptionsMenu()"); /* * Some bugs? This method seems to be called even after `onDestroy()`... */ if (getActivity() == null) return; /* * Sorting. */ final boolean sortAscending = DisplayPrefs.isSortAscending(getActivity()); MenuItem miSort = menu.findItem(R.id.afc_menuitem_sort); switch (DisplayPrefs.getSortType(getActivity())) { case BaseFile.SORT_BY_NAME: miSort.setIcon(Ui.resolveAttribute(getActivity(), sortAscending ? R.attr.afc_ic_menu_sort_by_name_asc : R.attr.afc_ic_menu_sort_by_name_desc)); break; case BaseFile.SORT_BY_SIZE: miSort.setIcon(Ui.resolveAttribute(getActivity(), sortAscending ? R.attr.afc_ic_menu_sort_by_size_asc : R.attr.afc_ic_menu_sort_by_size_desc)); break; case BaseFile.SORT_BY_MODIFICATION_TIME: miSort.setIcon(Ui.resolveAttribute(getActivity(), sortAscending ? R.attr.afc_ic_menu_sort_by_date_asc : R.attr.afc_ic_menu_sort_by_date_desc)); break; } /* * View type. */ MenuItem menuItem = menu.findItem(R.id.afc_menuitem_switch_viewmode); switch (DisplayPrefs.getViewType(getActivity())) { case GRID: menuItem.setIcon(Ui.resolveAttribute(getActivity(), R.attr.afc_ic_menu_listview)); menuItem.setTitle(R.string.afc_cmd_list_view); break; case LIST: menuItem.setIcon(Ui.resolveAttribute(getActivity(), R.attr.afc_ic_menu_gridview)); menuItem.setTitle(R.string.afc_cmd_grid_view); break; } /* * New folder. */ menu.findItem(R.id.afc_menuitem_new_folder).setEnabled(!mLoading); }// onPrepareOptionsMenu() @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.afc_menuitem_sort) resortViewFiles(); else if (item.getItemId() == R.id.afc_menuitem_new_folder) checkConditionsThenConfirmUserToCreateNewDir(); else if (item.getItemId() == R.id.afc_menuitem_switch_viewmode) switchViewType(); else if (item.getItemId() == R.id.afc_menuitem_home) goHome(); else return false; return true; }// onOptionsItemSelected() @Override public void onSaveInstanceState(Bundle outState) { outState.putParcelable(CURRENT_LOCATION, getCurrentLocation()); outState.putParcelable(HISTORY, mHistory); }// onSaveInstanceState() @Override public void onStop() { if (BuildConfig.DEBUG) Log.d(CLASSNAME, "onStop()"); super.onStop(); HistoryProviderUtils.doCleanupOutdatedHistoryItems(getActivity()); }// onStop() @Override public void onDestroy() { if (BuildConfig.DEBUG) Log.d(CLASSNAME, "onDestroy()"); super.onDestroy(); }// onDestroy() /* * LOADERMANAGER.LOADERCALLBACKS */ @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { mLoading = true; mNewLoader = true; mViewGroupFiles.setVisibility(View.GONE); mViewLoadingHandler.postDelayed(new Runnable() { @Override public void run() { mViewLoading.setVisibility(View.VISIBLE); }// run() }, DisplayPrefs.DELAY_TIME_FOR_SHORT_ANIMATION); getActivity().supportInvalidateOptionsMenu(); final Uri path = ((Uri) args.getParcelable(PATH)); buildAddressBar(path); String positiveRegex = getArguments().getString(FileChooserActivity.EXTRA_POSITIVE_REGEX_FILTER); String negativeRegex = getArguments().getString(FileChooserActivity.EXTRA_NEGATIVE_REGEX_FILTER); if (BuildConfig.DEBUG) Log.d(CLASSNAME, "onCreateLoader() >> path = " + path); return new CursorLoader(getActivity(), BaseFile.genContentUriBase(path.getAuthority()).buildUpon() .appendPath(path.getLastPathSegment()) .appendQueryParameter(BaseFile.PARAM_TASK_ID, Integer.toString(mIdLoaderData)) .appendQueryParameter(BaseFile.PARAM_SHOW_HIDDEN_FILES, Boolean.toString(getArguments().getBoolean(FileChooserActivity.EXTRA_DISPLAY_HIDDEN_FILES))) .appendQueryParameter(BaseFile.PARAM_FILTER_MODE, Integer.toString(mFilterMode)) .appendQueryParameter(BaseFile.PARAM_SORT_BY, Integer.toString(DisplayPrefs.getSortType(getActivity()))) .appendQueryParameter(BaseFile.PARAM_SORT_ASCENDING, Boolean.toString(DisplayPrefs.isSortAscending(getActivity()))) .appendQueryParameter(BaseFile.PARAM_LIMIT, Integer.toString(mMaxFileCount)) .appendQueryParameter(BaseFile.PARAM_POSITIVE_REGEX_FILTER, TextUtils.isEmpty(positiveRegex) ? "" : positiveRegex) .appendQueryParameter(BaseFile.PARAM_NEGATIVE_REGEX_FILTER, TextUtils.isEmpty(negativeRegex) ? "" : negativeRegex) .build(), null, null, null, null); }// onCreateLoader() @Override public void onLoadFinished(Loader<Cursor> loader, final Cursor data) { mLoading = false; /* * Update list view. */ mFileAdapter.changeCursor(data); mViewGroupFiles.setVisibility(View.VISIBLE); mViewLoadingHandler.removeCallbacksAndMessages(null); mViewLoading.setVisibility(View.GONE); getActivity().supportInvalidateOptionsMenu(); if (data == null) { showFooterView(true, getString(R.string.afc_msg_failed_please_try_again), true); return; } data.moveToLast(); final Uri uriInfo = BaseFileProviderUtils.getUri(data); final Uri selectedFile = (Uri) getArguments().getParcelable(FileChooserActivity.EXTRA_SELECT_FILE); if (selectedFile != null) getArguments().remove(FileChooserActivity.EXTRA_SELECT_FILE); /* * Footer. */ if (selectedFile != null && mIsSaveDialog) { new LoadingDialog<Void, Void, String>(getActivity(), false) { @Override protected String doInBackground(Void... params) { if (BaseFileProviderUtils.isFile(getActivity(), selectedFile)) return BaseFileProviderUtils.getFileName(getActivity(), selectedFile); return null; }// doInBackground() @Override protected void onPostExecute(String result) { super.onPostExecute(result); if (!TextUtils.isEmpty(result)) mTextSaveas.setText(result); }// onPostExecute() }.execute(); } // if boolean hasMoreFiles = ProviderUtils.getBooleanQueryParam(uriInfo, BaseFile.PARAM_HAS_MORE_FILES); showFooterView(hasMoreFiles || mFileAdapter.isEmpty(), hasMoreFiles ? getString(R.string.afc_pmsg_max_file_count_allowed, mMaxFileCount) : getString(R.string.afc_msg_empty), mFileAdapter.isEmpty()); if (mNewLoader || selectedFile != null) createFileSelector(); mNewLoader = false; }// onLoadFinished() @Override public void onLoaderReset(Loader<Cursor> loader) { /* * Cancel previous loader if there is one. */ cancelPreviousLoader(); mFileAdapter.changeCursor(null); mViewGroupFiles.setVisibility(View.GONE); mViewLoadingHandler.postDelayed(new Runnable() { @Override public void run() { mViewLoading.setVisibility(View.VISIBLE); }// run() }, DisplayPrefs.DELAY_TIME_FOR_SHORT_ANIMATION); getActivity().supportInvalidateOptionsMenu(); }// onLoaderReset() /** * Setup: * <p/> * <ul> * <li>title of activity;</li> * <li>button go back;</li> * <li>button location;</li> * <li>button go forward;</li> * </ul> */ private void setupHeader() { if (mBtnGoHome != null) mBtnGoHome.setOnClickListener(mBtnGoHomeOnClickListener); if (mIsSaveDialog) { getActivity().setTitle(R.string.afc_title_save_as); } else { switch (mFilterMode) { case BaseFile.FILTER_FILES_ONLY: getActivity().setTitle(getResources().getQuantityText(R.plurals.afc_title_choose_files, mIsMultiSelection ? 2 : 1)); break; case BaseFile.FILTER_FILES_AND_DIRECTORIES: getActivity().setTitle(getResources().getQuantityText(R.plurals.afc_title_choose_files_directories, mIsMultiSelection ? 2 : 1)); break; case BaseFile.FILTER_DIRECTORIES_ONLY: getActivity().setTitle(getResources().getQuantityText(R.plurals.afc_title_choose_directories, mIsMultiSelection ? 2 : 1)); break; } } // title of activity mViewGoBack.setEnabled(false); mViewGoBack.setOnClickListener(mBtnGoBackOnClickListener); mViewGoForward.setEnabled(false); mViewGoForward.setOnClickListener(mBtnGoForwardOnClickListener); }// setupHeader() /** * Setup: * <p/> * <ul> * <li>{@link #mViewFiles}</li> * <li>{@link #mViewFilesContainer}</li> * <li>{@link #mFileAdapter}</li> * </ul> */ private void setupViewFiles() { switch (DisplayPrefs.getViewType(getActivity())) { case GRID: mViewFiles = (AbsListView) getLayoutInflater(null).inflate(R.layout.afc_gridview_files, null); break; case LIST: mViewFiles = (AbsListView) getLayoutInflater(null).inflate(R.layout.afc_listview_files, null); break; } mViewFilesContainer.removeAllViews(); mViewFilesContainer.addView(mViewFiles, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1)); mViewFiles.setOnItemClickListener(mViewFilesOnItemClickListener); mViewFiles.setOnItemLongClickListener(mViewFilesOnItemLongClickListener); mViewFiles.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return mListviewFilesGestureDetector.onTouchEvent(event); } }); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB && !getActivity().getWindow().isFloating()) { mViewFiles.setCacheColorHint(getResources() .getColor(Ui.resolveAttribute(getActivity(), R.attr.afc_color_listview_cache_hint))); } /* * API 13+ does not recognize AbsListView.setAdapter(), so we cast it to * explicit class */ if (mViewFiles instanceof ListView) ((ListView) mViewFiles).setAdapter(mFileAdapter); else ((GridView) mViewFiles).setAdapter(mFileAdapter); // no comments :-D mFooterView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { E.show(getActivity()); return false; } }); }// setupViewFiles() /** * Setup: * <p/> * <ul> * <li>button Cancel;</li> * <li>text field "save as" filename;</li> * <li>button OK;</li> * </ul> */ private void setupFooter() { /* * By default, view group footer and all its child views are hidden. */ ViewGroup viewGroupFooterContainer = (ViewGroup) getView() .findViewById(R.id.afc_viewgroup_footer_container); ViewGroup viewGroupFooter = (ViewGroup) getView().findViewById(R.id.afc_viewgroup_footer); if (mIsSaveDialog) { viewGroupFooterContainer.setVisibility(View.VISIBLE); viewGroupFooter.setVisibility(View.VISIBLE); mTextSaveas.setVisibility(View.VISIBLE); mTextSaveas.setText(getArguments().getString(FileChooserActivity.EXTRA_DEFAULT_FILENAME)); mTextSaveas.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_DONE) { Ui.showSoftKeyboard(v, false); mBtnOk.performClick(); return true; } return false; }// onEditorAction() }); mTextSaveas.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { /* * Do nothing. */ }// onTextChanged() @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { /* * Do nothing. */ }// beforeTextChanged() @Override public void afterTextChanged(Editable s) { /* * If the user taps a file, the tag is set to that file's * URI. But if the user types the file name, we remove the * tag. */ mTextSaveas.setTag(null); }// afterTextChanged() }); mBtnOk.setVisibility(View.VISIBLE); mBtnOk.setOnClickListener(mBtnOk_SaveDialog_OnClickListener); mBtnOk.setBackgroundResource(Ui.resolveAttribute(getActivity(), R.attr.afc_selector_button_ok_saveas)); int size = getResources().getDimensionPixelSize(R.dimen.afc_button_ok_saveas_size); LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mBtnOk.getLayoutParams(); lp.width = size; lp.height = size; mBtnOk.setLayoutParams(lp); } // this is in save mode else { if (mIsMultiSelection) { viewGroupFooterContainer.setVisibility(View.VISIBLE); viewGroupFooter.setVisibility(View.VISIBLE); ViewGroup.LayoutParams lp = viewGroupFooter.getLayoutParams(); lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; viewGroupFooter.setLayoutParams(lp); mBtnOk.setMinWidth(getResources().getDimensionPixelSize(R.dimen.afc_single_button_min_width)); mBtnOk.setText(android.R.string.ok); mBtnOk.setVisibility(View.VISIBLE); mBtnOk.setOnClickListener(mBtnOk_OpenDialog_OnClickListener); } } // this is in open mode }// setupFooter() /** * Shows footer view. * * @param show * {@code true} or {@code false}. * @param text * the message you want to set. * @param center * {@code true} or {@code false}. */ @SuppressLint("InlinedApi") private void showFooterView(boolean show, String text, boolean center) { if (show) { mFooterView.setText(text); RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); if (!center) lp.addRule(RelativeLayout.ABOVE, R.id.afc_view_files_footer_view); mViewFilesContainer.setLayoutParams(lp); lp = (RelativeLayout.LayoutParams) mFooterView.getLayoutParams(); lp.addRule(RelativeLayout.CENTER_IN_PARENT, center ? 1 : 0); lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, center ? 0 : 1); mFooterView.setLayoutParams(lp); mFooterView.setVisibility(View.VISIBLE); } else mFooterView.setVisibility(View.GONE); }// showFooterView() /** * This should be called after the owner activity has been created * successfully. */ private void initGestureDetector() { mListviewFilesGestureDetector = new GestureDetector(getActivity(), new GestureDetector.SimpleOnGestureListener() { private Object getData(float x, float y) { int i = getSubViewId(x, y); if (i >= 0) return mViewFiles.getItemAtPosition(mViewFiles.getFirstVisiblePosition() + i); return null; }// getSubView() private int getSubViewId(float x, float y) { Rect r = new Rect(); for (int i = 0; i < mViewFiles.getChildCount(); i++) { mViewFiles.getChildAt(i).getHitRect(r); if (r.contains((int) x, (int) y)) return i; } return -1; }// getSubViewId() /** * Gets {@link Cursor} from {@code e}. * * @param e * {@link MotionEvent}. * @return the cursor, or {@code null} if not available. */ private Cursor getData(MotionEvent e) { Object o = getData(e.getX(), e.getY()); return o instanceof Cursor ? (Cursor) o : null; }// getDataModel() @Override public void onLongPress(MotionEvent e) { /* * Do nothing. */ }// onLongPress() @Override public boolean onSingleTapConfirmed(MotionEvent e) { /* * Do nothing. */ return false; }// onSingleTapConfirmed() @Override public boolean onDoubleTap(MotionEvent e) { if (mDoubleTapToChooseFiles) { if (mIsMultiSelection) return false; Cursor cursor = getData(e); if (cursor == null) return false; if (BaseFileProviderUtils.isDirectory(cursor) && BaseFile.FILTER_FILES_ONLY == mFilterMode) return false; /* * If mFilterMode == FILTER_DIRECTORIES_ONLY, files * won't be shown. */ if (mIsSaveDialog) { if (BaseFileProviderUtils.isFile(cursor)) { mTextSaveas.setText(BaseFileProviderUtils.getFileName(cursor)); /* * Always set tag after setting text, or tag * will be reset to null. */ mTextSaveas.setTag(BaseFileProviderUtils.getUri(cursor)); checkSaveasFilenameAndFinish(); } else return false; } else finish(BaseFileProviderUtils.getUri(cursor)); } // double tap to choose files else { /* * Do nothing. */ return false; } // single tap to choose files return true; }// onDoubleTap() @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { /* * Sometimes e1 or e2 can be null. This came from users' * experiences. */ if (e1 == null || e2 == null) return false; final int max_y_distance = 19;// 10 is too short :-D final int min_x_distance = 80; final int min_x_velocity = 200; if (Math.abs(e1.getY() - e2.getY()) < max_y_distance && Math.abs(e1.getX() - e2.getX()) > min_x_distance && Math.abs(velocityX) > min_x_velocity) { int pos = getSubViewId(e1.getX(), e1.getY()); if (pos >= 0) { /* * Don't let this event to be recognized as a * single tap. */ MotionEvent cancelEvent = MotionEvent.obtain(e1); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); mViewFiles.onTouchEvent(cancelEvent); deleteFile(mViewFiles.getFirstVisiblePosition() + pos); } } /* * Always return false to let the default handler draw * the item properly. */ return false; }// onFling() });// mListviewFilesGestureDetector }// initGestureDetector() /** * Connects to file provider service, then loads root directory. If can not, * then finishes this activity with result code = * {@link Activity#RESULT_CANCELED} * * @param savedInstanceState */ private void loadInitialPath(final Bundle savedInstanceState) { if (BuildConfig.DEBUG) Log.d(CLASSNAME, String.format("loadInitialPath() >> authority=[%s] | mRoot=[%s]", mFileProviderAuthority, mRoot)); /* * Priorities for starting path: * * 1. Current location (in case the activity has been killed after * configurations changed). * * 2. Selected file from key EXTRA_SELECT_FILE. * * 3. Root path from key EXTRA_ROOTPATH. * * 4. Last location. */ new LoadingDialog<Void, Uri, Bundle>(getActivity(), false) { /** * In onPostExecute(), if result is null then check this value. If * this is not null, show a toast and finish. If this is null, call * showCannotConnectToServiceAndWaitForTheUserToFinish(). */ String errMsg = null; boolean errorMessageInDialog = false; String log = ""; @Override protected Bundle doInBackground(Void... params) { /* * Current location */ Uri path = (Uri) (savedInstanceState != null ? savedInstanceState.getParcelable(CURRENT_LOCATION) : null); log += "savedInstanceState != null ? " + (savedInstanceState != null); log += "\npath != null ? " + (path != null); if (path != null) log += path; log += "\nmRoot != null ? " + (mRoot != null); if (mRoot != null) log += mRoot; if (mRoot != null) { Uri queryUri = BaseFile.genContentUriApi(mRoot.getAuthority()).buildUpon() .appendPath(BaseFile.CMD_CHECK_CONNECTION) .appendQueryParameter(BaseFile.PARAM_SOURCE, mRoot.getLastPathSegment()).build(); Cursor cursor = getActivity().getContentResolver().query(queryUri, null, null, null, null); log += "\ncursor != null ? " + (cursor != null); if (cursor != null) { cursor.moveToFirst(); errMsg = getString(R.string.afc_msg_cannot_connect_to_file_provider_service) + " " + " " + cursor.getString(0); errorMessageInDialog = true; return null; } } try { /* * Selected file */ log += "try selected file "; if (path == null) { path = (Uri) getArguments().getParcelable(FileChooserActivity.EXTRA_SELECT_FILE); if (path != null && BaseFileProviderUtils.fileExists(getActivity(), path)) path = BaseFileProviderUtils.getParentFile(getActivity(), path); } log += "success ? " + (path != null); /* * Rootpath */ log += "rootpath"; if ((path == null) || (!BaseFileProviderUtils.isDirectory(getActivity(), path))) { log += " assign"; path = mRoot; } if (path != null) { log += " path=" + path; log += " isdir?" + BaseFileProviderUtils.isDirectory(getActivity(), path); } /* * Last location */ if (path == null && DisplayPrefs.isRememberLastLocation(getActivity())) { String lastLocation = DisplayPrefs.getLastLocation(getActivity()); if (lastLocation != null) { path = Uri.parse(lastLocation); log += " from last loc"; } } if (path == null || !BaseFileProviderUtils.isDirectory(getActivity(), path)) { path = BaseFileProviderUtils.getDefaultPath(getActivity(), path == null ? mFileProviderAuthority : path.getAuthority()); log += " getDefault. path==null?" + (path == null); } } catch (Exception ex) { errMsg = getString(R.string.afc_msg_cannot_connect_to_file_provider_service) + " " + ex.toString(); errorMessageInDialog = true; return null; } if (path == null) { errMsg = "Did not find initial path."; errorMessageInDialog = true; return null; } if (BuildConfig.DEBUG) Log.d(CLASSNAME, "loadInitialPath() >> " + path); publishProgress(path); if (BaseFileProviderUtils.fileCanRead(getActivity(), path)) { Bundle result = new Bundle(); result.putParcelable(PATH, path); return result; } else { errorMessageInDialog = true; errMsg = getString(R.string.afc_pmsg_cannot_access_dir, BaseFileProviderUtils.getFileName(getActivity(), path)); } return null; }// doInBackground() @Override protected void onProgressUpdate(Uri... progress) { setCurrentLocation(progress[0]); }// onProgressUpdate() @Override protected void onPostExecute(Bundle result) { super.onPostExecute(result); if (result != null) { /* * Prepare the loader. Either re-connect with an existing * one, or start a new one. */ getLoaderManager().initLoader(mIdLoaderData, result, FragmentFiles.this); } else if (errMsg != null) { if (errorMessageInDialog) { Dlg.showError(getActivity(), errMsg, new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { getActivity().setResult(Activity.RESULT_FIRST_USER); getActivity().finish(); }// onCancel() }); } else { Dlg.toast(getActivity(), errMsg, Dlg.LENGTH_SHORT); getActivity().finish(); } } else showCannotConnectToServiceAndWaitForTheUserToFinish("loadInitialPath"); }// onPostExecute() }.execute(); }// loadInitialPath() /** * Checks if the fragment is loading files... * * @return {@code true} or {@code false}. */ public boolean isLoading() { return mLoading; }// isLoading() /** * Cancels the loader in progress. */ public void cancelPreviousLoader() { /* * Adds a fake path... */ if (getCurrentLocation() != null && getLoaderManager().getLoader(mIdLoaderData) != null) BaseFileProviderUtils.cancelTask(getActivity(), getCurrentLocation().getAuthority(), mIdLoaderData); mLoading = false; }// cancelPreviousLoader() /** * As the name means... */ private void showCannotConnectToServiceAndWaitForTheUserToFinish(String info) { Dlg.showError(getActivity(), getActivity().getString(R.string.afc_msg_cannot_connect_to_file_provider_service) + " " + info, new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { getActivity().setResult(Activity.RESULT_FIRST_USER); getActivity().finish(); }// onCancel() }); }// showCannotConnectToServiceAndWaitForTheUserToFinish() /** * Gets last location. * * @return the last location. */ private Uri getLastLocation() { return mLastLocation; }// getLastLocation() /** * Gets current location. * * @return the current location. */ private Uri getCurrentLocation() { return mCurrentLocation; }// getCurrentLocation() /** * Sets current location. * * @param location * the location to set. */ private void setCurrentLocation(Uri location) { /* * Do this so history's listener will retrieve the right current * location. */ mLastLocation = mCurrentLocation; mCurrentLocation = location; if (mHistory.indexOf(location) < 0) { mHistory.truncateAfter(mLastLocation); mHistory.push(location); } else mHistory.notifyHistoryChanged(); updateDbHistory(location); }// setCurrentLocation() private void goHome() { goTo(mRoot); }// goHome() private static final int[] BUTTON_SORT_IDS = { R.id.afc_button_sort_by_name_asc, R.id.afc_button_sort_by_name_desc, R.id.afc_button_sort_by_size_asc, R.id.afc_button_sort_by_size_desc, R.id.afc_button_sort_by_date_asc, R.id.afc_button_sort_by_date_desc }; /** * Show a dialog for sorting options and resort file list after user * selected an option. */ private void resortViewFiles() { final Dialog dialog = new Dialog(getActivity(), Ui.resolveAttribute(getActivity(), R.attr.afc_theme_dialog)); dialog.setCanceledOnTouchOutside(true); // get the index of button of current sort type int btnCurrentSortTypeIdx = 0; switch (DisplayPrefs.getSortType(getActivity())) { case BaseFile.SORT_BY_NAME: btnCurrentSortTypeIdx = 0; break; case BaseFile.SORT_BY_SIZE: btnCurrentSortTypeIdx = 2; break; case BaseFile.SORT_BY_MODIFICATION_TIME: btnCurrentSortTypeIdx = 4; break; } if (!DisplayPrefs.isSortAscending(getActivity())) btnCurrentSortTypeIdx++; View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View v) { dialog.dismiss(); if (v.getId() == R.id.afc_button_sort_by_name_asc) { DisplayPrefs.setSortType(getActivity(), BaseFile.SORT_BY_NAME); DisplayPrefs.setSortAscending(getActivity(), true); } else if (v.getId() == R.id.afc_button_sort_by_name_desc) { DisplayPrefs.setSortType(getActivity(), BaseFile.SORT_BY_NAME); DisplayPrefs.setSortAscending(getActivity(), false); } else if (v.getId() == R.id.afc_button_sort_by_size_asc) { DisplayPrefs.setSortType(getActivity(), BaseFile.SORT_BY_SIZE); DisplayPrefs.setSortAscending(getActivity(), true); } else if (v.getId() == R.id.afc_button_sort_by_size_desc) { DisplayPrefs.setSortType(getActivity(), BaseFile.SORT_BY_SIZE); DisplayPrefs.setSortAscending(getActivity(), false); } else if (v.getId() == R.id.afc_button_sort_by_date_asc) { DisplayPrefs.setSortType(getActivity(), BaseFile.SORT_BY_MODIFICATION_TIME); DisplayPrefs.setSortAscending(getActivity(), true); } else if (v.getId() == R.id.afc_button_sort_by_date_desc) { DisplayPrefs.setSortType(getActivity(), BaseFile.SORT_BY_MODIFICATION_TIME); DisplayPrefs.setSortAscending(getActivity(), false); } /* * Reload current location. */ goTo(getCurrentLocation()); getActivity().supportInvalidateOptionsMenu(); }// onClick() };// listener View view = getLayoutInflater(null).inflate(R.layout.afc_settings_sort_view, null); for (int i = 0; i < BUTTON_SORT_IDS.length; i++) { View v = view.findViewById(BUTTON_SORT_IDS[i]); v.setOnClickListener(listener); if (i == btnCurrentSortTypeIdx) { v.setEnabled(false); if (v instanceof Button) ((Button) v).setText(R.string.afc_bullet); } } dialog.setTitle(R.string.afc_title_sort_by); dialog.setContentView(view); dialog.show(); }// resortViewFiles() /** * Switch view type between {@link ViewType#LIST} and {@link ViewType#GRID} */ private void switchViewType() { switch (DisplayPrefs.getViewType(getActivity())) { case GRID: DisplayPrefs.setViewType(getActivity(), ViewType.LIST); break; case LIST: DisplayPrefs.setViewType(getActivity(), ViewType.GRID); break; } setupViewFiles(); getActivity().supportInvalidateOptionsMenu(); goTo(getCurrentLocation()); }// switchViewType() /** * Checks current conditions to see if we can create new directory. Then * confirms user to do so. */ private void checkConditionsThenConfirmUserToCreateNewDir() { if (LocalFileContract.getAuthority(getActivity()).equals(mFileProviderAuthority) && !Utils.hasPermissions(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) { Dlg.toast(getActivity(), R.string.afc_msg_app_doesnot_have_permission_to_create_files, Dlg.LENGTH_SHORT); return; } new LoadingDialog<Void, Void, Boolean>(getActivity(), false) { @Override protected Boolean doInBackground(Void... params) { return getCurrentLocation() != null && BaseFileProviderUtils.fileCanWrite(getActivity(), getCurrentLocation()); }// doInBackground() @Override protected void onPostExecute(Boolean result) { super.onPostExecute(result); if (result) showNewDirectoryCreationDialog(); else Dlg.toast(getActivity(), R.string.afc_msg_cannot_create_new_folder_here, Dlg.LENGTH_SHORT); }// onProgressUpdate() }.execute(); }// checkConditionsThenConfirmUserToCreateNewDir() /** * Confirms user to create new directory. */ private void showNewDirectoryCreationDialog() { final AlertDialog dialog = Dlg.newAlertDlg(getActivity()); View view = getLayoutInflater(null).inflate(R.layout.afc_simple_text_input_view, null); final EditText textFile = (EditText) view.findViewById(R.id.afc_text1); textFile.setHint(R.string.afc_hint_folder_name); textFile.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_DONE) { Ui.showSoftKeyboard(v, false); dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick(); return true; } return false; } }); dialog.setView(view); dialog.setTitle(R.string.afc_cmd_new_folder); dialog.setIcon(android.R.drawable.ic_menu_add); dialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(android.R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { final String name = textFile.getText().toString().trim(); if (!FileUtils.isFilenameValid(name)) { Dlg.toast(getActivity(), getString(R.string.afc_pmsg_filename_is_invalid, name), Dlg.LENGTH_SHORT); return; } new LoadingDialog<Void, Void, Uri>(getActivity(), false) { @Override protected Uri doInBackground(Void... params) { return getActivity().getContentResolver().insert( BaseFile.genContentUriBase(getCurrentLocation().getAuthority()).buildUpon() .appendPath(getCurrentLocation().getLastPathSegment()) .appendQueryParameter(BaseFile.PARAM_NAME, name) .appendQueryParameter(BaseFile.PARAM_FILE_TYPE, Integer.toString(BaseFile.FILE_TYPE_DIRECTORY)) .build(), null); }// doInBackground() @Override protected void onPostExecute(Uri result) { super.onPostExecute(result); if (result != null) { Dlg.toast(getActivity(), getString(R.string.afc_msg_done), Dlg.LENGTH_SHORT); } else Dlg.toast(getActivity(), getString(R.string.afc_pmsg_cannot_create_folder, name), Dlg.LENGTH_SHORT); }// onPostExecute() }.execute(); }// onClick() }); dialog.show(); Ui.showSoftKeyboard(textFile, true); final Button buttonOk = dialog.getButton(DialogInterface.BUTTON_POSITIVE); buttonOk.setEnabled(false); textFile.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { /* * Do nothing. */ }// onTextChanged() @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { /* * Do nothing. */ }// beforeTextChanged() @Override public void afterTextChanged(Editable s) { buttonOk.setEnabled(FileUtils.isFilenameValid(s.toString().trim())); }// afterTextChanged() }); }// showNewDirectoryCreationDialog() /** * Deletes a file. * * @param position * the position of item to be delete. */ private void deleteFile(final int position) { Cursor cursor = (Cursor) mFileAdapter.getItem(position); /* * The cursor can be changed if the list view is updated, so we take its * properties here. */ final boolean isFile = BaseFileProviderUtils.isFile(cursor); final String filename = BaseFileProviderUtils.getFileName(cursor); if (!BaseFileProviderUtils.fileCanWrite(cursor)) { Dlg.toast(getActivity(), getString(R.string.afc_pmsg_cannot_delete_file, isFile ? getString(R.string.afc_file) : getString(R.string.afc_folder), filename), Dlg.LENGTH_SHORT); return; } if (LocalFileContract.getAuthority(getActivity()).equals(mFileProviderAuthority) && !Utils.hasPermissions(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) { Dlg.toast(getActivity(), R.string.afc_msg_app_doesnot_have_permission_to_delete_files, Dlg.LENGTH_SHORT); return; } /* * The cursor can be changed if the list view is updated, so we take its * properties here. */ final int id = cursor.getInt(cursor.getColumnIndex(BaseFile._ID)); final Uri uri = BaseFileProviderUtils.getUri(cursor); mFileAdapter.markItemAsDeleted(id, true); Dlg.confirmYesno(getActivity(), getString(R.string.afc_pmsg_confirm_delete_file, isFile ? getString(R.string.afc_file) : getString(R.string.afc_folder), filename), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { new LoadingDialog<Void, Void, Boolean>(getActivity(), getString(R.string.afc_pmsg_deleting_file, isFile ? getString(R.string.afc_file) : getString(R.string.afc_folder), filename), true) { final int taskId = EnvUtils.genId(); private void notifyFileDeleted() { Dlg.toast(getActivity(), getString(R.string.afc_pmsg_file_has_been_deleted, isFile ? getString(R.string.afc_file) : getString(R.string.afc_folder), filename), Dlg.LENGTH_SHORT); }// notifyFileDeleted() @Override protected Boolean doInBackground(Void... params) { getActivity().getContentResolver().delete(uri.buildUpon() .appendQueryParameter(BaseFile.PARAM_TASK_ID, Integer.toString(taskId)) .build(), null, null); return !BaseFileProviderUtils.fileExists(getActivity(), uri); }// doInBackground() @Override protected void onCancelled() { if (getCurrentLocation() != null) BaseFileProviderUtils.cancelTask(getActivity(), getCurrentLocation().getAuthority(), taskId); new LoadingDialog<Void, Void, Boolean>(getActivity(), false) { @Override protected Boolean doInBackground(Void... params) { return BaseFileProviderUtils.fileExists(getActivity(), uri); }// doInBackground() @Override protected void onPostExecute(Boolean result) { super.onPostExecute(result); if (result) { mFileAdapter.markItemAsDeleted(id, false); Dlg.toast(getActivity(), R.string.afc_msg_cancelled, Dlg.LENGTH_SHORT); } else notifyFileDeleted(); }// onPostExecute() }.execute(); super.onCancelled(); }// onCancelled() @Override protected void onPostExecute(Boolean result) { super.onPostExecute(result); if (result) { notifyFileDeleted(); } else { mFileAdapter.markItemAsDeleted(id, false); Dlg.toast(getActivity(), getString(R.string.afc_pmsg_cannot_delete_file, isFile ? getString(R.string.afc_file) : getString(R.string.afc_folder), filename), Dlg.LENGTH_SHORT); } }// onPostExecute() }.execute();// LoadingDialog }// onClick() }, new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { mFileAdapter.markItemAsDeleted(id, false); }// onCancel() }); }// deleteFile() /** * As the name means. */ private void checkSaveasFilenameAndFinish() { new LoadingDialog<Void, String, Uri>(getActivity(), false) { String filename; Uri fileUri; int fileType; @Override protected void onPreExecute() { super.onPreExecute(); /* * If the user tapped a file, its URI was stored here. If not, * this is null. */ fileUri = (Uri) mTextSaveas.getTag(); /* * File name and extension. */ filename = mTextSaveas.getText().toString().trim(); if (fileUri == null && getArguments().containsKey(FileChooserActivity.EXTRA_DEFAULT_FILE_EXT)) { if (!TextUtils.isEmpty(filename)) { String ext = getArguments().getString(FileChooserActivity.EXTRA_DEFAULT_FILE_EXT); if (!filename.matches("(?si)^.+" + Pattern.quote(Texts.C_PERIOD + ext) + "$")) { filename += Texts.C_PERIOD + ext; mTextSaveas.setText(filename); } } } }// onPreExecute() @Override protected Uri doInBackground(Void... params) { if (!BaseFileProviderUtils.fileCanWrite(getActivity(), getCurrentLocation())) { publishProgress(getString(R.string.afc_msg_cannot_save_a_file_here)); return null; } if (fileUri == null && !FileUtils.isFilenameValid(filename)) { publishProgress(getString(R.string.afc_pmsg_filename_is_invalid, filename)); return null; } if (fileUri == null) fileUri = getCurrentLocation().buildUpon() .appendQueryParameter(BaseFile.PARAM_APPEND_NAME, filename).build(); final Cursor cursor = getActivity().getContentResolver().query(fileUri, null, null, null, null); try { if (cursor == null || !cursor.moveToFirst()) return null; fileType = cursor.getInt(cursor.getColumnIndex(BaseFile.COLUMN_TYPE)); return BaseFileProviderUtils.getUri(cursor); } finally { if (cursor != null) cursor.close(); } }// doInBackground() @Override protected void onProgressUpdate(String... progress) { Dlg.toast(getActivity(), progress[0], Dlg.LENGTH_SHORT); }// onProgressUpdate() @Override protected void onPostExecute(final Uri result) { super.onPostExecute(result); if (result == null) { /* * TODO ? */ return; } switch (fileType) { case BaseFile.FILE_TYPE_DIRECTORY: { Dlg.toast(getActivity(), getString(R.string.afc_pmsg_filename_is_directory, filename), Dlg.LENGTH_SHORT); break; } // FILE_TYPE_DIRECTORY case BaseFile.FILE_TYPE_FILE: { Dlg.confirmYesno(getActivity(), getString(R.string.afc_pmsg_confirm_replace_file, filename), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { finish(result, true); }// onClick() }); break; } // FILE_TYPE_FILE case BaseFile.FILE_TYPE_NOT_EXISTED: { finish(result, false); break; } // FILE_TYPE_NOT_EXISTED } }// onPostExecute() }.execute(); }// checkSaveasFilenameAndFinish() /** * Goes to a specified location. * * @param dir * a directory, of course. * @since v4.3 beta */ private void goTo(final Uri dir) { new LoadingDialog<Uri, String, Bundle>(getActivity(), false) { /** * In onPostExecute(), if result is null then check this value. If * this is not null, show a toast. If this is null, call * showCannotConnectToServiceAndWaitForTheUserToFinish(). */ String errMsg = null; @Override protected Bundle doInBackground(Uri... params) { if (params[0] == null) params[0] = BaseFileProviderUtils.getDefaultPath(getActivity(), mFileProviderAuthority); if (params[0] == null) return null; /* * Check if the path of `params[0]` is same as current location, * then set `params[0]` to current location. This avoids of * pushing two same paths into history, because we compare the * pointers (not the paths) when pushing it to history. */ if (params[0].equals(getCurrentLocation())) params[0] = getCurrentLocation(); if (BaseFileProviderUtils.fileCanRead(getActivity(), params[0])) { /* * Cancel previous loader if there is one. */ cancelPreviousLoader(); Bundle bundle = new Bundle(); bundle.putParcelable(PATH, params[0]); return bundle; } // if errMsg = getString(R.string.afc_pmsg_cannot_access_dir, BaseFileProviderUtils.getFileName(getActivity(), params[0])); return null; }// doInBackground() @Override protected void onPostExecute(Bundle result) { super.onPostExecute(result); if (result != null) { setCurrentLocation((Uri) result.getParcelable(PATH)); getLoaderManager().restartLoader(mIdLoaderData, result, FragmentFiles.this); } else if (errMsg != null) { Dlg.toast(getActivity(), errMsg, Dlg.LENGTH_SHORT); } else showCannotConnectToServiceAndWaitForTheUserToFinish("goTo: " + dir.toString()); }// onPostExecute() }.execute(dir); }// goTo() /** * Updates or inserts {@code path} into history database. */ private void updateDbHistory(Uri path) { if (BuildConfig.DEBUG) Log.d(CLASSNAME, "updateDbHistory() >> path = " + path); Calendar cal = Calendar.getInstance(); final long beginTodayMillis = cal.getTimeInMillis() - (cal.get(Calendar.HOUR_OF_DAY) * 60 * 60 * 1000 + cal.get(Calendar.MINUTE) * 60 * 1000 + cal.get(Calendar.SECOND) * 1000); if (BuildConfig.DEBUG) { Log.d(CLASSNAME, String.format("beginToday = %s (%s)", DbUtils.formatNumber(beginTodayMillis), new Date(beginTodayMillis))); Log.d(CLASSNAME, String.format("endToday = %s (%s)", DbUtils.formatNumber(beginTodayMillis + DateUtils.DAY_IN_MILLIS), new Date(beginTodayMillis + DateUtils.DAY_IN_MILLIS))); } /* * Does the update and returns the number of rows updated. */ long time = new Date().getTime(); ContentValues values = new ContentValues(); values.put(HistoryContract.COLUMN_PROVIDER_ID, BaseFileProviderUtils.getProviderId(path.getAuthority())); values.put(HistoryContract.COLUMN_FILE_TYPE, BaseFile.FILE_TYPE_DIRECTORY); values.put(HistoryContract.COLUMN_URI, path.toString()); values.put(HistoryContract.COLUMN_MODIFICATION_TIME, DbUtils.formatNumber(time)); int count = getActivity().getContentResolver().update(HistoryContract.genContentUri(getActivity()), values, String.format("%s >= '%s' and %s < '%s' and %s = %s and %s like %s", HistoryContract.COLUMN_MODIFICATION_TIME, DbUtils.formatNumber(beginTodayMillis), HistoryContract.COLUMN_MODIFICATION_TIME, DbUtils.formatNumber(beginTodayMillis + DateUtils.DAY_IN_MILLIS), HistoryContract.COLUMN_PROVIDER_ID, DatabaseUtils.sqlEscapeString(values.getAsString(HistoryContract.COLUMN_PROVIDER_ID)), HistoryContract.COLUMN_URI, DatabaseUtils.sqlEscapeString(values.getAsString(HistoryContract.COLUMN_URI))), null); if (count <= 0) { values.put(HistoryContract.COLUMN_CREATE_TIME, DbUtils.formatNumber(time)); getActivity().getContentResolver().insert(HistoryContract.genContentUri(getActivity()), values); } }// updateDbHistory() /** * As the name means. */ private void buildAddressBar(final Uri path) { if (path == null) return; mViewAddressBar.removeAllViews(); new LoadingDialog<Void, Cursor, Void>(getActivity(), false) { LinearLayout.LayoutParams lpBtnLoc; LinearLayout.LayoutParams lpDivider; LayoutInflater inflater = getLayoutInflater(null); final int dim = getResources().getDimensionPixelSize(R.dimen.afc_5dp); int count = 0; @Override protected void onPreExecute() { super.onPreExecute(); lpBtnLoc = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); lpBtnLoc.gravity = Gravity.CENTER; }// onPreExecute() @Override protected Void doInBackground(Void... params) { Cursor cursor = getActivity().getContentResolver().query(path, null, null, null, null); while (cursor != null) { if (cursor.moveToFirst()) { publishProgress(cursor); cursor.close(); } else break; /* * Process the parent directory. */ Uri uri = Uri.parse(cursor.getString(cursor.getColumnIndex(BaseFile.COLUMN_URI))); cursor = getActivity().getContentResolver() .query(BaseFile.genContentUriApi(uri.getAuthority()).buildUpon() .appendPath(BaseFile.CMD_GET_PARENT) .appendQueryParameter(BaseFile.PARAM_SOURCE, uri.getLastPathSegment()).build(), null, null, null, null); } // while return null; }// doInBackground() @Override protected void onProgressUpdate(Cursor... progress) { /* * Add divider. */ if (mViewAddressBar.getChildCount() > 0) { View divider = inflater.inflate(R.layout.afc_view_locations_divider, null); if (lpDivider == null) { lpDivider = new LinearLayout.LayoutParams(dim, dim); lpDivider.gravity = Gravity.CENTER; lpDivider.setMargins(dim, dim, dim, dim); } mViewAddressBar.addView(divider, 0, lpDivider); } Uri lastUri = Uri.parse(progress[0].getString(progress[0].getColumnIndex(BaseFile.COLUMN_URI))); TextView btnLoc = (TextView) inflater.inflate(R.layout.afc_button_location, null); String name = BaseFileProviderUtils.getFileName(progress[0]); btnLoc.setText(TextUtils.isEmpty(name) ? getString(R.string.afc_root) : name); btnLoc.setTag(lastUri); btnLoc.setOnClickListener(mBtnLocationOnClickListener); btnLoc.setOnLongClickListener(mBtnLocationOnLongClickListener); mViewAddressBar.addView(btnLoc, 0, lpBtnLoc); if (count++ == 0) { Rect r = new Rect(); btnLoc.getPaint().getTextBounds(name, 0, name.length(), r); if (r.width() >= getResources().getDimensionPixelSize(R.dimen.afc_button_location_max_width) - btnLoc.getPaddingLeft() - btnLoc.getPaddingRight()) { mTextFullDirName .setText(progress[0].getString(progress[0].getColumnIndex(BaseFile.COLUMN_NAME))); mTextFullDirName.setVisibility(View.VISIBLE); } else mTextFullDirName.setVisibility(View.GONE); } // if }// onProgressUpdate() @Override protected void onPostExecute(Void result) { super.onPostExecute(result); /* * Sometimes without delay time, it doesn't work... */ mViewLocationsContainer.postDelayed(new Runnable() { public void run() { mViewLocationsContainer.fullScroll(HorizontalScrollView.FOCUS_RIGHT); }// run() }, DisplayPrefs.DELAY_TIME_FOR_VERY_SHORT_ANIMATION); }// onPostExecute() }.execute(); }// buildAddressBar() /** * Finishes this activity when save-as. * * @param file * @link Uri. */ private void finish(Uri file, boolean fileExists) { ArrayList<Uri> list = new ArrayList<Uri>(); list.add(file); Intent intent = new Intent(); intent.setData(file); intent.putParcelableArrayListExtra(FileChooserActivity.EXTRA_RESULTS, list); intent.putExtra(FileChooserActivity.EXTRA_RESULT_FILE_EXISTS, fileExists); getActivity().setResult(FileChooserActivity.RESULT_OK, intent); getActivity().finish(); }// finish() /** * Finishes this activity. * * @param files * list of {@link Uri}. */ private void finish(Uri... files) { List<Uri> list = new ArrayList<Uri>(); for (Uri uri : files) list.add(uri); finish((ArrayList<Uri>) list); }// finish() /** * Finishes this activity. * * @param files * list of {@link Uri}. */ private void finish(ArrayList<Uri> files) { if (files == null || files.isEmpty()) { getActivity().setResult(Activity.RESULT_CANCELED); getActivity().finish(); return; } Intent intent = new Intent(); if (files.size() == 1) { intent.setData(files.get(0)); } intent.putParcelableArrayListExtra(FileChooserActivity.EXTRA_RESULTS, files); getActivity().setResult(FileChooserActivity.RESULT_OK, intent); if (DisplayPrefs.isRememberLastLocation(getActivity()) && getCurrentLocation() != null) DisplayPrefs.setLastLocation(getActivity(), getCurrentLocation().toString()); else DisplayPrefs.setLastLocation(getActivity(), null); getActivity().finish(); }// finish() /* * ========================================================================= * BUTTON LISTENERS * ========================================================================= */ private final View.OnClickListener mBtnGoHomeOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { goHome(); }// onClick() };// mBtnGoHomeOnClickListener private final View.OnClickListener mBtnGoBackOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { /* * If user deleted a dir which was one in history, then maybe there * are duplicates, so we check and remove them here. */ Uri currentLoc = getCurrentLocation(); Uri preLoc = null; while (currentLoc.equals(preLoc = mHistory.prevOf(currentLoc))) mHistory.remove(preLoc); if (preLoc != null) goTo(preLoc); else mViewGoBack.setEnabled(false); } };// mBtnGoBackOnClickListener private final View.OnClickListener mBtnLocationOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { if (v.getTag() instanceof Uri) { goTo((Uri) v.getTag()); } }// onClick() };// mBtnLocationOnClickListener private final View.OnLongClickListener mBtnLocationOnLongClickListener = new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { if (BaseFile.FILTER_FILES_ONLY == mFilterMode || mIsSaveDialog) return false; finish((Uri) v.getTag()); return false; }// onLongClick() };// mBtnLocationOnLongClickListener private final View.OnClickListener mBtnGoForwardOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { /* * If user deleted a dir which was one in history, then maybe there * are duplicates, so we check and remove them here. */ Uri currentLoc = getCurrentLocation(); Uri nextLoc = null; while (currentLoc.equals(nextLoc = mHistory.nextOf(currentLoc))) mHistory.remove(nextLoc); if (nextLoc != null) goTo(nextLoc); else mViewGoForward.setEnabled(false); }// onClick() };// mBtnGoForwardOnClickListener private final View.OnClickListener mBtnOk_SaveDialog_OnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { Ui.showSoftKeyboard(v, false); checkSaveasFilenameAndFinish(); }// onClick() };// mBtnOk_SaveDialog_OnClickListener private final View.OnClickListener mBtnOk_OpenDialog_OnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { finish(mFileAdapter.getSelectedItems()); }// onClick() };// mBtnOk_OpenDialog_OnClickListener /* * FRAGMENT LISTENERS */ /* * LISTVIEW HELPER */ private final AdapterView.OnItemClickListener mViewFilesOnItemClickListener = new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Cursor cursor = (Cursor) mFileAdapter.getItem(position); if (BaseFileProviderUtils.isDirectory(cursor)) { goTo(BaseFileProviderUtils.getUri(cursor)); return; } if (mIsSaveDialog) { mTextSaveas.setText(BaseFileProviderUtils.getFileName(cursor)); /* * Always set tag after setting text, or tag will be reset to * null. */ mTextSaveas.setTag(BaseFileProviderUtils.getUri(cursor)); } if (mDoubleTapToChooseFiles) { /* * Do nothing. */ return; } // double tap to choose files else { if (mIsMultiSelection) return; if (mIsSaveDialog) checkSaveasFilenameAndFinish(); else finish(BaseFileProviderUtils.getUri(cursor)); } // single tap to choose files }// onItemClick() };// mViewFilesOnItemClickListener private final AdapterView.OnItemLongClickListener mViewFilesOnItemLongClickListener = new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { Cursor cursor = (Cursor) mFileAdapter.getItem(position); if (mDoubleTapToChooseFiles) { // do nothing } // double tap to choose files else { if (!mIsSaveDialog && !mIsMultiSelection && BaseFileProviderUtils.isDirectory(cursor) && (BaseFile.FILTER_DIRECTORIES_ONLY == mFilterMode || BaseFile.FILTER_FILES_AND_DIRECTORIES == mFilterMode)) { finish(BaseFileProviderUtils.getUri(cursor)); } } // single tap to choose files /* * Notify that we already handled long click here. */ return true; }// onItemLongClick() };// mViewFilesOnItemLongClickListener /** * We use a {@link LoadingDialog} to avoid of * {@code NetworkOnMainThreadException}. */ private LoadingDialog<Void, Void, Integer> mFileSelector; /** * Creates new {@link #mFileSelector} to select appropriate file after * loading a folder's content. It's either the parent path of last path, or * the file provided by key {@link FileChooserActivity#EXTRA_SELECT_FILE}. * Note that this also cancels previous selector if there is such one. */ private void createFileSelector() { if (mFileSelector != null) mFileSelector.cancel(true); mFileSelector = new LoadingDialog<Void, Void, Integer>(getActivity(), true) { @Override protected Integer doInBackground(Void... params) { final Cursor cursor = mFileAdapter.getCursor(); if (cursor == null || cursor.isClosed()) return -1; final Uri selectedFile = (Uri) getArguments().getParcelable(FileChooserActivity.EXTRA_SELECT_FILE); final int colUri = cursor.getColumnIndex(BaseFile.COLUMN_URI); if (selectedFile != null) getArguments().remove(FileChooserActivity.EXTRA_SELECT_FILE); int shouldBeSelectedIdx = -1; final Uri uri = selectedFile != null ? selectedFile : getLastLocation(); if (uri == null || !BaseFileProviderUtils.fileExists(getActivity(), uri)) return -1; final String fileName = BaseFileProviderUtils.getFileName(getActivity(), uri); if (fileName == null) return -1; Uri parentUri = BaseFileProviderUtils.getParentFile(getActivity(), uri); if ((uri == getLastLocation() && !getCurrentLocation().equals(getLastLocation()) && BaseFileProviderUtils.isAncestorOf(getActivity(), getCurrentLocation(), uri)) || getCurrentLocation().equals(parentUri)) { if (cursor.moveToFirst()) { while (!cursor.isLast()) { if (isCancelled()) return -1; Uri subUri = Uri.parse(cursor.getString(colUri)); if (uri == getLastLocation()) { if (cursor.getInt(cursor .getColumnIndex(BaseFile.COLUMN_TYPE)) == BaseFile.FILE_TYPE_DIRECTORY) { if (subUri.equals(uri) || BaseFileProviderUtils.isAncestorOf(getActivity(), subUri, uri)) { shouldBeSelectedIdx = Math.max(0, cursor.getPosition() - 2); break; } } } else { if (uri.equals(subUri)) { shouldBeSelectedIdx = Math.max(0, cursor.getPosition() - 2); break; } } cursor.moveToNext(); } // while } // if } // if return shouldBeSelectedIdx; }// doInBackground() @Override protected void onPostExecute(final Integer result) { super.onPostExecute(result); if (isCancelled() || mFileAdapter.isEmpty()) return; /* * Use a Runnable to make sure this works. Because if the list * view is handling data, this might not work. * * Also sometimes it doesn't work without a delay. */ mViewFiles.postDelayed(new Runnable() { @Override public void run() { if (result >= 0 && result < mFileAdapter.getCount()) mViewFiles.setSelection(result); else if (!mFileAdapter.isEmpty()) mViewFiles.setSelection(0); }// run() }, DisplayPrefs.DELAY_TIME_FOR_VERY_SHORT_ANIMATION); }// onPostExecute() }; mFileSelector.execute(); }// createFileSelector() }