package com.orgzly.android.ui.fragments.browser; import android.content.Intent; import android.content.res.TypedArray; import android.os.Bundle; import android.os.Environment; import android.support.annotation.StringRes; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.TextView; import com.orgzly.BuildConfig; import com.orgzly.R; import com.orgzly.android.LocalStorage; import com.orgzly.android.ui.CommonActivity; import com.orgzly.android.ui.ReposActivity; import com.orgzly.android.util.AppPermissions; import com.orgzly.android.util.LogUtils; import java.io.File; import java.io.FilenameFilter; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * */ public class FileBrowserFragment extends BrowserFragment { private static final String TAG = FileBrowserFragment.class.getName(); /** Name used for {@link android.app.FragmentManager}. */ public static final String FRAGMENT_TAG = FileBrowserFragment.class.getName(); private static final FilenameFilter FILENAME_FILTER = new FilenameFilter() { @Override public boolean accept(File dir, String filename) { File f = new File(dir, filename); return (f.isFile() || f.isDirectory()) && !f.isHidden(); } }; public static FileBrowserFragment getInstance(String entry) { FileBrowserFragment fragment = new FileBrowserFragment(); if (entry != null) { fragment.init(entry); } return fragment; } /** * Mandatory empty constructor for the fragment manager to instantiate the * fragment (e.g. upon screen orientation changes). */ public FileBrowserFragment() { } public String defaultPath() { File file = Environment.getExternalStorageDirectory(); boolean isRW = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, "Default path " + file + " is " + (isRW ? "" : "NOT ") + "MOUNTED"); String path = null; if (isRW) { path = file.getAbsolutePath(); } return path; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); tryLoadFileListFromNext(true); } @Override void setupShortcuts(final LinearLayout layout) { layout.removeAllViews(); /* * First shortcut changes current directory to default. */ Button primaryStorageButton = createShortcutButton(R.string.primary_storage); primaryStorageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, "first shortcut clicked"); mNextItem = defaultPath(); tryLoadFileListFromNext(true); } }); layout.addView(primaryStorageButton); /* * If we can access secondary storage and it exists, create a shortcut for it. * TODO: Support more then one? */ if (LocalStorage.isSecondaryStorageAccessible(getContext())) { final Button secondaryStorageButton = createShortcutButton(R.string.secondary_storage); secondaryStorageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, "second shortcut clicked"); // Check if secondary storage is still accessible if (LocalStorage.isSecondaryStorageAccessible(getContext())) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); getActivity().startActivityForResult(intent, ReposActivity.ACTION_OPEN_DOCUMENT_TREE_REQUEST_CODE); } mListener.onBrowserCancel(); } else { layout.removeView(secondaryStorageButton); ((CommonActivity) getActivity()).showSimpleSnackbarLong(R.string.secondary_storage_not_available_any_more); } } }); layout.addView(secondaryStorageButton); } } private Button createShortcutButton(@StringRes int id) { Button button = new Button(getContext()); button.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1)); button.setText(id); return button; } @Override public void onResume() { if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG); super.onResume(); /* Check for permissions. */ AppPermissions.isGrantedOrRequest((CommonActivity) getActivity(), AppPermissions.FOR_LOCAL_REPO); } @Override public void onListItemClick(ListView l, View v, int position, long id) { Item item = (Item) l.getItemAtPosition(position); if (mCurrentItem != null) { if (item.isUp) { File path = new File(mCurrentItem); if (path.getParentFile() != null) { mNextItem = path.getParentFile().getAbsolutePath(); tryLoadFileListFromNext(false); } } else { File sel = new File(mCurrentItem, item.name); if (sel.isDirectory()) { mNextItem = sel.getAbsolutePath(); tryLoadFileListFromNext(false); } } } else { Log.e(TAG, "Clicked on " + item.name + " but there is no current directory set"); } } private File[] fileList(String path) { if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, "Trying to get a list of files in " + path); if (path != null) { File file = new File(path); if (file.exists()) { return file.listFiles(FILENAME_FILTER); } } return null; } /** * Populates {@link #mItemList} with non-hidden files and directories from {@link #mNextItem}. * Creates a new adapter and uses it for list view. */ private void tryLoadFileListFromNext(boolean fallbackToDefaultOrRoot) { File[] fileList; fileList = fileList(mNextItem); if (fileList == null) { /* Try default path. */ /* Do not try alternative paths. * Used when clicking from already opened browser. * Don't do anything in that case. */ if (! fallbackToDefaultOrRoot) { return; } mNextItem = defaultPath(); fileList = fileList(mNextItem); if (fileList == null) { /* Try root. */ mNextItem = "/"; fileList = fileList(mNextItem); if (fileList == null) { fileList = new File[0]; } } } doLoadFileListFromNext(fileList); setupAdapter(); } private void doLoadFileListFromNext(File[] files) { if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, "Loading file list for " + mNextItem); File nextPath = new File(mNextItem); List<File> list = Arrays.asList(files); Collections.sort(list, new FileTypeComparator()); TypedArray typedArray = getActivity().obtainStyledAttributes(R.styleable.Icons); mItemList = new Item[list.size() + 1]; mItemList[0] = new Item(typedArray.getResourceId(R.styleable.Icons_oic_browser_up, 0)); for (int i = 0; i < list.size(); i++) { mItemList[i+1] = new Item(typedArray.getResourceId(R.styleable.Icons_oic_browser_file, 0), list.get(i).getName()); /* Update icon if it's a directory. */ File sel = new File(nextPath, list.get(i).getName()); if (sel.isDirectory()) { mItemList[i+1].icon = typedArray.getResourceId(R.styleable.Icons_oic_browser_directory, 0); } } typedArray.recycle(); /* Current item updated. */ mCurrentItemView.setText(nextPath.getAbsolutePath()); mItemHistory.add(mCurrentItem); mCurrentItem = mNextItem; } private void setupAdapter() { // TODO: Must create every time, can we update itemList only? ListAdapter adapter = new ArrayAdapter<Item>(getActivity(), R.layout.item_browser, mItemList) { @Override public View getView(int position, View convertView, ViewGroup parent) { // View view = super.getView(position, convertView, parent); if (convertView == null) { convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_browser, parent, false); } ImageView imageView = (ImageView) convertView.findViewById(R.id.browser_item_icon); TextView textView = (TextView) convertView.findViewById(R.id.browser_item_name); imageView.setImageResource(mItemList[position].icon); textView.setText(mItemList[position].name); return convertView; } }; getListView().setAdapter(adapter); } public void refresh() { mNextItem = mCurrentItem; tryLoadFileListFromNext(false); } class FileTypeComparator implements Comparator<File> { @Override public int compare(File file1, File file2) { /* Same type. */ if ((file1.isDirectory() && file2.isDirectory()) || (file1.isFile() && file2.isFile())) { return String.CASE_INSENSITIVE_ORDER.compare(file1.getName(), file2.getName()); } if (file1.isDirectory() && file2.isFile()) { return -1; } else { return 1; } } } }