Java tutorial
/* * Copyright (C) 2018 George Venios * Copyright (C) 2012 OpenIntents.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.veniosg.dir.android.fragment; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ContentValues; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.support.v4.app.DialogFragment; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.ListView; import android.widget.Toast; import android.widget.ViewFlipper; import com.veniosg.dir.IntentConstants; import com.veniosg.dir.R; import com.veniosg.dir.android.FileManagerApplication; import com.veniosg.dir.android.adapter.FileHolderListAdapter; import com.veniosg.dir.android.dialog.CreateDirectoryDialog; import com.veniosg.dir.android.dialog.DetailsDialog; import com.veniosg.dir.android.dialog.MultiCompressDialog; import com.veniosg.dir.android.dialog.MultiDeleteDialog; import com.veniosg.dir.android.dialog.RenameDialog; import com.veniosg.dir.android.dialog.SingleCompressDialog; import com.veniosg.dir.android.dialog.SingleDeleteDialog; import com.veniosg.dir.android.provider.BookmarkProvider; import com.veniosg.dir.android.service.ZipService; import com.veniosg.dir.android.ui.PathController; import com.veniosg.dir.android.ui.widget.AnimatedFileListContainer; import com.veniosg.dir.android.ui.widget.PathView; import com.veniosg.dir.android.util.CopyHelper; import com.veniosg.dir.android.util.FileUtils; import com.veniosg.dir.android.util.Logger; import com.veniosg.dir.android.util.MediaScannerUtils; import com.veniosg.dir.android.util.Utils; import com.veniosg.dir.mvvm.model.FileHolder; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import static android.view.View.GONE; import static android.view.View.VISIBLE; import static com.veniosg.dir.android.fragment.PreferenceFragment.getMediaScanFromPreference; import static com.veniosg.dir.android.ui.PathController.MANUAL_INPUT; import static com.veniosg.dir.android.ui.PathController.OnDirectoryChangedListener; import static com.veniosg.dir.android.ui.Themer.setStatusBarColour; import static com.veniosg.dir.android.ui.widget.PathView.ActivityProvider; import static com.veniosg.dir.android.util.CopyHelper.COPY; import static com.veniosg.dir.android.util.FileUtils.isZipArchive; /** * A file list fragment that supports CAB selection. */ public class SimpleFileListFragment extends FileListFragment { private static final String INSTANCE_STATE_PATHBAR_MODE = "pathbar_mode"; private static final int REQUEST_CODE_MULTISELECT = 2; private static HashMap<String, ScrollPosition> sScrollPositions = new HashMap<String, ScrollPosition>(); private PathController mPathBar; private AnimatedFileListContainer mTransitionView; private ActionMode mActionMode; private boolean mActionsEnabled = true; private int mNavigationDirection = 0; private View heroView; private AbsListView.MultiChoiceModeListener mMultiChoiceModeListener = new AbsListView.MultiChoiceModeListener() { @Override public boolean onPrepareActionMode(android.view.ActionMode mode, Menu menu) { if (getView() == null) return false; // Prevent crash from getListView()'s ensureList(). menu.clear(); switch (getListView().getCheckedItemCount()) { // Single selection case 1: FileHolder fHolder = (FileHolder) getListAdapter().getItem(getCheckedItemPosition()); if (fHolder == null) return false; File file = fHolder.getFile(); inflateSingleChoiceMenu(mode.getMenuInflater(), menu); if (file.isDirectory()) { menu.removeItem(R.id.menu_send); } if (!isZipArchive(file)) { menu.findItem(R.id.menu_more_options).getSubMenu().removeItem(R.id.menu_extract); } else { menu.findItem(R.id.menu_more_options).getSubMenu().removeItem(R.id.menu_compress); } break; // Multiple selection default: inflateMultipleChoiceMenu(mode.getMenuInflater(), menu); // If all items are directories boolean foldersOnly = true; for (FileHolder fileHolder : getCheckedItems()) { foldersOnly &= fileHolder.getFile().isDirectory(); } if (foldersOnly) { menu.removeItem(R.id.menu_send); } break; } return true; } @Override public void onDestroyActionMode(android.view.ActionMode mode) { mActionMode = null; mPathBar.setEnabled(true); setStatusBarColour(getActivity(), false); } @Override public boolean onCreateActionMode(android.view.ActionMode mode, Menu menu) { mActionMode = mode; mPathBar.setEnabled(false); setStatusBarColour(getActivity(), true); return true; } @Override public boolean onActionItemClicked(android.view.ActionMode mode, MenuItem item) { if (item.getItemId() == R.id.menu_select_all) { for (int i = 0; i < getListView().getCount(); i++) getListView().setItemChecked(i, true); return true; } switch (getListView().getCheckedItemCount()) { // Single selection case 1: return handleSingleSelectionAction(mode, item); // Multiple selection default: return handleMultipleSelectionAction(mode, item); } } @Override public void onItemCheckedStateChanged(android.view.ActionMode mode, int position, long id, boolean checked) { if (getListView().getCheckedItemCount() != 0) { mode.setTitle(getListView().getCheckedItemCount() + " " + getString(R.string.selected)); // Force actions' refresh mode.invalidate(); } } }; private FileHolderListAdapter.OnItemToggleListener mOnItemToggleListener = new FileHolderListAdapter.OnItemToggleListener() { @Override public void onItemToggle(int position) { getListView().setItemChecked(position, !Utils.getItemChecked(getListView(), position)); } }; private ActivityProvider mActivityProvider = new ActivityProvider() { @Override public Activity getActivity() { return SimpleFileListFragment.this.getActivity(); } }; private boolean handleMultipleSelectionAction(ActionMode mode, MenuItem item) { DialogFragment dialog; Bundle args; ArrayList<FileHolder> fItems = getCheckedItems(); switch (item.getItemId()) { case R.id.menu_send: mode.finish(); Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE); ArrayList<Uri> uris = new ArrayList<Uri>(); intent.setType("text/plain"); for (FileHolder fh : fItems) { if (!fh.getFile().isDirectory()) uris.add(FileUtils.getUri(fh)); } intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); try { startActivity(Intent.createChooser(intent, getString(R.string.send_chooser_title))); return true; } catch (ActivityNotFoundException e) { Toast.makeText(getActivity(), R.string.send_not_available, Toast.LENGTH_SHORT).show(); return true; } case R.id.menu_delete: mode.finish(); dialog = new MultiDeleteDialog(); dialog.setTargetFragment(this, 0); args = new Bundle(); args.putParcelableArrayList(IntentConstants.EXTRA_DIALOG_FILE_HOLDER, new ArrayList<Parcelable>(fItems)); dialog.setArguments(args); dialog.show(getFragmentManager(), MultiDeleteDialog.class.getName()); return true; case R.id.menu_move: mode.finish(); ((FileManagerApplication) getActivity().getApplication()).getCopyHelper().cut(fItems); getActivity().supportInvalidateOptionsMenu(); return true; case R.id.menu_copy: mode.finish(); ((FileManagerApplication) getActivity().getApplication()).getCopyHelper().copy(fItems); getActivity().supportInvalidateOptionsMenu(); return true; case R.id.menu_compress: mode.finish(); dialog = new MultiCompressDialog(); dialog.setTargetFragment(this, 0); args = new Bundle(); args.putParcelableArrayList(IntentConstants.EXTRA_DIALOG_FILE_HOLDER, new ArrayList<Parcelable>(fItems)); dialog.setArguments(args); dialog.show(getFragmentManager(), MultiCompressDialog.class.getName()); return true; default: return false; } } private boolean handleSingleSelectionAction(ActionMode mode, MenuItem item) { FileHolder fItem = (FileHolder) getListAdapter().getItem(getCheckedItemPosition()); if (fItem == null) return false; DialogFragment dialog; Bundle args; switch (item.getItemId()) { case R.id.menu_create_shortcut: mode.finish(); Utils.createShortcut(fItem, getActivity()); return true; case R.id.menu_move: mode.finish(); ((FileManagerApplication) getActivity().getApplication()).getCopyHelper().cut(fItem); getActivity().supportInvalidateOptionsMenu(); return true; case R.id.menu_copy: mode.finish(); ((FileManagerApplication) getActivity().getApplication()).getCopyHelper().copy(fItem); getActivity().supportInvalidateOptionsMenu(); return true; case R.id.menu_delete: mode.finish(); dialog = new SingleDeleteDialog(); dialog.setTargetFragment(SimpleFileListFragment.this, 0); args = new Bundle(); args.putParcelable(IntentConstants.EXTRA_DIALOG_FILE_HOLDER, fItem); dialog.setArguments(args); dialog.show(getFragmentManager(), SingleDeleteDialog.class.getName()); return true; case R.id.menu_rename: mode.finish(); dialog = new RenameDialog(); dialog.setTargetFragment(SimpleFileListFragment.this, 0); args = new Bundle(); args.putParcelable(IntentConstants.EXTRA_DIALOG_FILE_HOLDER, fItem); dialog.setArguments(args); dialog.show(getFragmentManager(), RenameDialog.class.getName()); return true; case R.id.menu_send: mode.finish(); Utils.sendFile(fItem, getActivity()); return true; case R.id.menu_details: mode.finish(); dialog = new DetailsDialog(); dialog.setTargetFragment(this, 0); args = new Bundle(); args.putParcelable(IntentConstants.EXTRA_DIALOG_FILE_HOLDER, fItem); dialog.setArguments(args); dialog.show(getFragmentManager(), DetailsDialog.class.getName()); return true; case R.id.menu_compress: mode.finish(); dialog = new SingleCompressDialog(); dialog.setTargetFragment(this, 0); args = new Bundle(); args.putParcelable(IntentConstants.EXTRA_DIALOG_FILE_HOLDER, fItem); dialog.setArguments(args); dialog.show(getFragmentManager(), SingleCompressDialog.class.getName()); return true; case R.id.menu_extract: mode.finish(); File extractTo = new File(fItem.getFile().getParentFile(), FileUtils.getNameWithoutExtension(fItem.getFile())); extractTo.mkdirs(); // We just extract on the current directory. If the user needs to put it in another dir, // he/she can copy/cut the file ZipService.extractTo(getActivity(), fItem, extractTo); return true; case R.id.menu_bookmark: mode.finish(); addBookmark(fItem.getFile()); return true; default: return false; } } private void addBookmark(File file) { String path = file.getAbsolutePath(); Cursor query = getActivity().getContentResolver().query(BookmarkProvider.CONTENT_URI, new String[] { BookmarkProvider._ID }, BookmarkProvider.PATH + "=?", new String[] { path }, null); if (query != null) { if (!query.moveToFirst()) { ContentValues values = new ContentValues(); values.put(BookmarkProvider.NAME, file.getName()); values.put(BookmarkProvider.PATH, path); getActivity().getContentResolver().insert(BookmarkProvider.CONTENT_URI, values); } query.close(); } Activity act = getActivity(); if (act instanceof SideNavFragment.BookmarkContract) { ((SideNavFragment.BookmarkContract) act).showBookmarks(); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_filelist_simple, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // We expect the pathbar to add the bottom shadow in this case View toolbar = getActivity().findViewById(R.id.toolbar); if (toolbar != null) toolbar.setElevation(0); PathView pathView = (PathView) getActivity().findViewById(R.id.pathview); pathView.setActivityProvider(mActivityProvider); mPathBar = pathView; mTransitionView = (AnimatedFileListContainer) view.findViewById(R.id.zoomview); final View backButton = view.findViewById(R.id.empty_img); // Handle mPath differently if we restore state or just initially create the view. if (savedInstanceState == null) { mPathBar.setInitialDirectory(getPath()); } else { mPathBar.cd(getPath()); } mPathBar.setOnDirectoryChangedListener(new OnDirectoryChangedListener() { @Override public void directoryChanged(File newCurrentDir) { open(new FileHolder(newCurrentDir, getActivity())); backButton.setVisibility(mPathBar.isParentDirectoryNavigable() ? VISIBLE : GONE); } }); if (savedInstanceState != null && savedInstanceState.getBoolean(INSTANCE_STATE_PATHBAR_MODE)) { mPathBar.switchToManualInput(); } // Removed else clause as the other mode is the default. initContextualActions(); // For animations' sake ((ViewFlipper) view.findViewById(R.id.flipper)).setInAnimation(null); ((ViewFlipper) view.findViewById(R.id.flipper)).setOutAnimation(null); } private void initContextualActions() { if (mActionsEnabled) { getListView().setMultiChoiceModeListener(mMultiChoiceModeListener); getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); ((FileHolderListAdapter) getListAdapter()).setOnItemToggleListener(mOnItemToggleListener); setHasOptionsMenu(true); } } @Override public void onListItemClick(AbsListView l, View v, int position, long id) { FileHolder item = (FileHolder) mAdapter.getItem(position); if (item != null) { heroView = v; openInformingPathBar(item); heroView = null; } } /** * Use this to open files and folders using this fragment. Appropriately handles pathbar updates. * * @param item The dir/file to open. */ public void openInformingPathBar(FileHolder item) { openInformingPathBar(item, false); } public void openInformingPathBar(FileHolder item, boolean forceNoAnim) { if (mPathBar == null) open(item); else mPathBar.cd(item.getFile(), forceNoAnim); } /** * Point this Fragment to show the contents of the passed file. * * @param f If same as current, does nothing. */ private void open(FileHolder f) { if (!f.getFile().exists()) return; if (f.getFile().isDirectory()) { openDir(f); } else if (f.getFile().isFile()) { openFile(f); } } private void openFile(FileHolder fileholder) { FileUtils.openFile(fileholder, getActivity()); } /** * Attempts to open a directory for browsing. * Override this to handle folder click behavior. * * @param fileHolder The holder of the directory to open. */ private void openDir(FileHolder fileHolder) { // Avoid unnecessary attempts to load. if (fileHolder.getFile().getAbsolutePath().equals(getPath())) return; if (getListAdapter().getCount() > 0) { keepFolderScroll(); } // Save required data for animation mNavigationDirection = Utils.getNavigationDirection(new File(getPath()), fileHolder.getFile()); mTransitionView.setupAnimations(mNavigationDirection, heroView); // Load setPath(fileHolder.getFile()); refresh(); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.options_filelist_simple, menu); } @Override public void onPrepareOptionsMenu(Menu menu) { if (getActivity() == null) { return; } boolean showMediaScanMenuItem = getMediaScanFromPreference(getActivity()); if (hasScanner() && !isScannerRunning() && showMediaScanMenuItem) { // We only know about ".nomedia" once scanning is finished. boolean noMedia = isMediaScannerDisabledForPath(); menu.findItem(R.id.menu_media_scan_include).setVisible(noMedia); menu.findItem(R.id.menu_media_scan_exclude).setVisible(!noMedia); } else { menu.findItem(R.id.menu_media_scan_include).setVisible(false); menu.findItem(R.id.menu_media_scan_exclude).setVisible(false); } CopyHelper copyHelper = ((FileManagerApplication) getActivity().getApplication()).getCopyHelper(); MenuItem pasteAction = menu.findItem(R.id.menu_paste); if (copyHelper.canPaste()) { int stringResource = (copyHelper.getOperationType() == COPY ? R.plurals.menu_copy_items_to : R.plurals.menu_move_items_to); pasteAction.setTitle(getResources().getQuantityString(stringResource, copyHelper.getItemCount(), copyHelper.getItemCount())); pasteAction.getIcon().setLevel(copyHelper.getItemCount()); pasteAction.setVisible(true); menu.findItem(R.id.menu_clear_clipboard).setVisible(true); } else { pasteAction.setVisible(false); menu.findItem(R.id.menu_clear_clipboard).setVisible(false); } } @Override protected void onDataApplied() { super.onDataApplied(); if (getActivity() != null) { getActivity().invalidateOptionsMenu(); } } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_clear_clipboard: ((FileManagerApplication) getActivity().getApplication()).getCopyHelper().clear(); getActivity().supportInvalidateOptionsMenu(); return true; case R.id.menu_create_folder: CreateDirectoryDialog dialog = new CreateDirectoryDialog(); dialog.setTargetFragment(this, 0); Bundle args = new Bundle(); args.putString(IntentConstants.EXTRA_DIR_PATH, getPath()); dialog.setArguments(args); dialog.show(getActivity().getSupportFragmentManager(), CreateDirectoryDialog.class.getName()); return true; case R.id.menu_media_scan_include: includeInMediaScan(); return true; case R.id.menu_media_scan_exclude: excludeFromMediaScan(); return true; case R.id.menu_bookmark: addBookmark(new File(getPath())); return true; case R.id.menu_paste: if (((FileManagerApplication) getActivity().getApplication()).getCopyHelper().canPaste()) { ((FileManagerApplication) getActivity().getApplication()).getCopyHelper() .paste(getActivity().getApplicationContext(), new File(getPath())); } else { Toast.makeText(getActivity(), R.string.nothing_to_paste, Toast.LENGTH_LONG).show(); } getActivity().supportInvalidateOptionsMenu(); return true; default: return false; } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { // Automatically refresh to display possible changes done through the multiselect fragment. if (requestCode == REQUEST_CODE_MULTISELECT) refresh(); super.onActivityResult(requestCode, resultCode, data); } private void includeInMediaScan() { // Delete the .nomedia file. File file = new File(mPathBar.getCurrentDirectory(), FileUtils.NOMEDIA_FILE_NAME); if (file.delete()) { MediaScannerUtils.informFolderAdded(getActivity().getApplicationContext(), file.getParentFile()); Toast.makeText(getActivity(), getString(R.string.media_library_added), Toast.LENGTH_LONG).show(); } else { // That didn't work. Toast.makeText(getActivity(), getString(R.string.error_generic), Toast.LENGTH_LONG).show(); } refresh(); } private void excludeFromMediaScan() { // Create the .nomedia file. File file = new File(mPathBar.getCurrentDirectory(), FileUtils.NOMEDIA_FILE_NAME); try { if (file.createNewFile()) { MediaScannerUtils.informFolderDeleted(getActivity().getApplicationContext(), file.getParentFile()); Toast.makeText(getActivity(), getString(R.string.media_library_removed), Toast.LENGTH_LONG).show(); } else { Toast.makeText(getActivity(), getString(R.string.error_media_scan), Toast.LENGTH_LONG).show(); } } catch (IOException e) { // That didn't work. Logger.log(e); Toast.makeText(getActivity(), getString(R.string.error_media_scan), Toast.LENGTH_LONG).show(); } refresh(); } public void browseToHome() { mPathBar.cd(mPathBar.getInitialDirectory()); } public boolean pressBack() { return mPathBar.onBackPressed(); } /** * Set whether to show menu and selection actions. Must be set before OnViewCreated is called. * * @param enabled Whether the actions are to be shown. */ public void setActionsEnabled(boolean enabled) { mActionsEnabled = enabled; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(INSTANCE_STATE_PATHBAR_MODE, mPathBar.getMode() == MANUAL_INPUT); } /** * Override to inflate more items for the single choiceCAB. Always call the super implementation. * * @param menuInflater The inflater to use. * @param menu The menu to inflate to. */ void inflateSingleChoiceMenu(MenuInflater menuInflater, Menu menu) { menuInflater.inflate(R.menu.cab_single, menu); } /** * Override to inflate more items for the multiple choice CAB. Always call the super implementation. * * @param menuInflater The inflater to use. * @param menu The menu to inflate to. */ void inflateMultipleChoiceMenu(MenuInflater menuInflater, Menu menu) { menuInflater.inflate(R.menu.cab_multi, menu); } /** * This is error free only when FileHolderListAdapter uses stableIds and getItemId(int) returns the int passed (the position of the item). * * @return */ int getCheckedItemPosition() { return (int) getListView().getCheckedItemIds()[0]; } private void useFolderScroll(final ScrollPosition pos) { if (getView() != null) { Utils.scrollToPosition(getListView(), pos, false); } } private void keepFolderScroll() { int firstVisiblePosition = getListView().getFirstVisiblePosition(); View firstVisibleChild = getListView().getChildAt(0); if (firstVisibleChild != null) { sScrollPositions.put(getPath(), new ScrollPosition(firstVisiblePosition, firstVisibleChild.getTop())); } } /** * @return A {@link FileHolder} list with the currently selected items. */ private ArrayList<FileHolder> getCheckedItems() { ArrayList<FileHolder> items = new ArrayList<FileHolder>(); for (long pos : getListView().getCheckedItemIds()) { FileHolder item = (FileHolder) getListAdapter().getItem((int) pos); if (item != null) items.add(item); } return items; } @Override protected void onListVisibilityChanging(boolean visible) { if (visible) { if (sScrollPositions.containsKey(getPath())) { ScrollPosition pos = sScrollPositions.get(getPath()); useFolderScroll(pos); } else { useFolderScroll(new ScrollPosition(0, 0)); } } } @Override protected void onListVisibilityChanged(boolean visible) { if (!visible && mTransitionView != null) { if (mNavigationDirection == 1) { mTransitionView.animateFwd(); } else if (mNavigationDirection == -1) { mTransitionView.animateBwd(); } else { // Do not animate. mTransitionView.clearAnimations(); } } } @Override protected void onEmptyViewClicked() { if (mPathBar.isParentDirectoryNavigable()) { mPathBar.cd(mPathBar.getParentDirectory()); } } public void closeActionMode() { if (mActionMode != null) { mActionMode.finish(); } } public static class ScrollPosition { public int index; public int top; public ScrollPosition(int index, int top) { this.index = index; this.top = top; } } }