/*
* Copyright 2014 Yaroslav Mytkalyk
* 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.docd.purefm.ui.fragments;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.widget.SwipeRefreshLayout;
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.AdapterView;
import android.widget.GridView;
import android.widget.ListView;
import com.cyanogenmod.filemanager.util.MediaHelper;
import com.docd.purefm.R;
import com.docd.purefm.adapters.BrowserBaseAdapter;
import com.docd.purefm.adapters.BrowserGridAdapter;
import com.docd.purefm.adapters.BrowserListAdapter;
import com.docd.purefm.browser.Browser;
import com.docd.purefm.browser.Browser.OnNavigateListener;
import com.docd.purefm.controller.ActionModeController;
import com.docd.purefm.controller.MenuController;
import com.docd.purefm.file.FileFactory;
import com.docd.purefm.file.GenericFile;
import com.docd.purefm.settings.Settings;
import com.docd.purefm.tasks.DirectoryScanTask;
import com.docd.purefm.ui.activities.AbstractBrowserActivity;
import com.docd.purefm.utils.ClipBoard;
import com.docd.purefm.utils.PFMFileUtils;
import com.docd.purefm.utils.ThemeUtils;
import com.docd.purefm.ui.view.BreadCrumbTextView.OnSequenceClickListener;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.io.File;
/**
* Fragment manages file List, menu and ActionMode.
* @author Doctoror
*/
public final class BrowserFragment extends UserVisibleHintFragment
implements SwipeRefreshLayout.OnRefreshListener {
private static final String STATE_BROWSER = "BrowserFragment.state.mBrowser";
//private ActionBar actionBar;
private ActionModeController actionModeController;
private MenuController menuController;
private Browser mBrowser;
private BrowserBaseAdapter mAdapter;
private OnNavigateListener mParentOnNavigateListener;
private OnSequenceClickListener mOnSequenceListener;
private SwipeRefreshLayout mSwipeRefreshLayoutList;
private SwipeRefreshLayout mSwipeRefreshLayoutEmpty;
private AbsListView mListView;
private View mMainProgress;
private DirectoryScanTask mScannerTask;
private boolean mRefreshFlag;
/**
* If Browser is not yet initialized, initial state will be saved to this field
*/
private Parcelable mBrowserInitialState;
private int mPrevId;
private boolean mFirstRun;
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (mListView instanceof GridView) {
((GridView) mListView).setNumColumns(ThemeUtils.getInteger(
getActivity().getTheme(), R.attr.browserGridColumns));
}
}
@Override
public void onAttach(final Activity activity) {
super.onAttach(activity);
if (activity instanceof AbstractBrowserActivity) {
mBrowser = new Browser((AbstractBrowserActivity) activity,
((AbstractBrowserActivity) activity).isHistoryEnabled());
} else {
throw new IllegalStateException(
"BrowserFragment should be attached only to BrowserPagerActivity");
}
mBrowser.setOnNavigateListener(new OnNavigateListener() {
@Override
public void onNavigate(GenericFile path) {
if (mParentOnNavigateListener != null) {
mParentOnNavigateListener.onNavigate(path);
}
if (isResumedAndVisible()) {
startScan();
} else {
mRefreshFlag = true;
}
}
@Override
public void onNavigationCompleted(GenericFile path) {
if (mMainProgress.getVisibility() == View.VISIBLE) {
mMainProgress.setVisibility(View.GONE);
}
if (mFirstRun) {
mFirstRun = false;
}
if (mRefreshFlag) {
mRefreshFlag = false;
}
if (mParentOnNavigateListener != null) {
mParentOnNavigateListener.onNavigationCompleted(path);
}
}
});
mBrowser.restoreState(mBrowserInitialState);
mBrowserInitialState = null;
mOnSequenceListener = new OnSequenceClickListener() {
@Override
public void onSequenceClick(String sequence) {
final GenericFile target = FileFactory.newFile(
Settings.getInstance(getBrowserActivity()), sequence);
mBrowser.navigate(target, true);
}
};
menuController = new MenuController((AbstractBrowserActivity) activity, mBrowser);
actionModeController = new ActionModeController(activity);
}
@NonNull
private AbstractBrowserActivity getBrowserActivity() {
final AbstractBrowserActivity a = (AbstractBrowserActivity) getActivity();
if (a == null) {
throw new IllegalStateException("Not attached to the activity");
}
return a;
}
@Override
public void onDetach() {
super.onDetach();
mParentOnNavigateListener = null;
}
@Override
public void onDestroy() {
super.onDestroy();
mAdapter.dropCaches();
}
@Override
public void onCreate(Bundle state) {
setRetainInstance(true);
setHasOptionsMenu(true);
super.onCreate(state);
mFirstRun = true;
restoreManualState(state);
}
public void restoreManualState(final Bundle state) {
if (state != null) {
state.setClassLoader(BrowserFragment.class.getClassLoader());
final BrowserFragment.SavedState savedState = state.getParcelable(STATE_BROWSER);
if (savedState != null) {
if (savedState.mBrowserState != null) {
if (mBrowser != null) {
mBrowser.restoreState(savedState.mBrowserState);
} else {
mBrowserInitialState = savedState.mBrowserState;
}
}
mPrevId = savedState.mPrevId;
}
}
}
public void saveManualState(final Bundle outState) {
final Parcelable browserState;
if (mBrowser == null) {
browserState = null;
} else {
browserState = mBrowser.saveInstanceState();
}
outState.putParcelable(STATE_BROWSER, new SavedState(mPrevId, browserState));
}
public boolean onBackPressed() {
return mBrowser != null && mBrowser.back();
}
@Override
public void onActivityCreated(Bundle state) {
super.onActivityCreated(state);
actionModeController.setListView(mListView);
mParentOnNavigateListener = getBrowserActivity();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
final AbstractBrowserActivity activity = getBrowserActivity();
if (activity.shouldShowBrowserFragmentMenu()) {
inflater.inflate(R.menu.browser, menu);
// TODO it returns true even on devices that don't have the physical key.
// Find a better method to detect search hardware button
//if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_SEARCH)) {
// menu.removeItem(R.id.menu_search);
//}
final MenuItem content = menu.findItem(android.R.id.content);
if (content == null) {
throw new RuntimeException("Inflated menu item should contain android.R.id.content");
}
final Settings settings = Settings.getInstance(activity);
switch (settings.getListAppearance()) {
case LIST:
content.setIcon(ThemeUtils.getDrawableNonNull(activity, R.attr.ic_menu_view_as_grid))
.setTitle(R.string.menu_view_as_grid)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
break;
case GRID:
content.setIcon(ThemeUtils.getDrawableNonNull(activity, R.attr.ic_menu_view_as_list))
.setTitle(R.string.menu_view_as_list)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
break;
default:
throw new IllegalArgumentException("Unexpected ListAppearance:" +
settings.getListAppearance());
}
if (ClipBoard.isEmpty()) {
menu.removeItem(android.R.id.paste);
}
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return menuController.onMenuItemSelected(item);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View parent = inflater.inflate(R.layout.fragment_browser, container, false);
if (parent == null) {
throw new RuntimeException("Inflated view is null");
}
initList(inflater, parent);
mFirstRun = true;
return parent;
}
private void initList(@NonNull final LayoutInflater inflater, @NonNull final View parent) {
final AbstractBrowserActivity context = getBrowserActivity();
if (mListView != null) {
final View emptyView = mListView.getEmptyView();
if (emptyView != null) {
emptyView.setVisibility(View.GONE);
}
mListView.setVisibility(View.GONE);
}
final Settings settings = Settings.getInstance(context);
final ViewGroup listContainer = (ViewGroup) parent.findViewById(R.id.list_container);
if (listContainer == null) {
throw new RuntimeException("parent should contain ViewGroup with id R.id.list_container");
}
final View swipeRefreshList;
switch (settings.getListAppearance()) {
case LIST:
swipeRefreshList = inflater.inflate(R.layout.browser_listview, listContainer);
break;
case GRID:
swipeRefreshList = inflater.inflate(R.layout.browser_gridview, listContainer);
break;
default:
throw new IllegalArgumentException("Unexpected ListAppearance: " +
settings.getListAppearance());
}
if (swipeRefreshList == null) {
throw new RuntimeException("Inflated View is null");
}
mMainProgress = parent.findViewById(android.R.id.progress);
mSwipeRefreshLayoutList = (SwipeRefreshLayout) swipeRefreshList.findViewById(
R.id.browser_list_swipe_refresh);
mSwipeRefreshLayoutEmpty = (SwipeRefreshLayout) parent.findViewById(android.R.id.empty);
mListView = (AbsListView) mSwipeRefreshLayoutList.getChildAt(0);
if (mListView instanceof ListView) {
mAdapter = new BrowserListAdapter(context);
} else {
mAdapter = new BrowserGridAdapter(context);
}
menuController.setBrowserAdapter(this.mAdapter);
mListView.setEmptyView(parent.findViewById(android.R.id.empty));
mListView.setAdapter(this.mAdapter);
final View emptyView = mListView.getEmptyView();
if (emptyView != null) {
emptyView.setVisibility(View.GONE);
}
mListView.setVisibility(View.GONE);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> av, View v, int pos, long id) {
final GenericFile target = (GenericFile) (av
.getItemAtPosition(pos));
if (target == null) {
throw new RuntimeException("onItemClick(): item at position is null");
}
if (target.isDirectory()) {
mBrowser.navigate(target, true);
} else {
final AbstractBrowserActivity activity = getBrowserActivity();
if (activity.getGetContentMimeType() == null) {
PFMFileUtils.openFileInExternalApp(activity, target.toFile());
} else {
final Intent intent = new Intent();
intent.setData(getResultUriForFileFromIntent(activity.getContentResolver(),
target.toFile(), activity.getIntent()));
activity.setResult(Activity.RESULT_OK, intent);
activity.finish();
}
}
}
/*
* Copyright (C) 2013 The CyanogenMod Project
*
* 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.
*/
private Uri getResultUriForFileFromIntent(ContentResolver cr, File src, Intent intent) {
// Try to find the preferred uri scheme
Uri result = MediaHelper.fileToContentUri(cr, src);
if (result == null) {
result = Uri.fromFile(src);
}
if (Intent.ACTION_PICK.equals(intent.getAction()) && intent.getData() != null) {
final String scheme = intent.getData().getScheme();
if (scheme != null) {
result = result.buildUpon().scheme(scheme).build();
}
}
return result;
}
});
mListView.setChoiceMode(context.getGetContentMimeType() == null ?
AbsListView.CHOICE_MODE_MULTIPLE_MODAL : AbsListView.CHOICE_MODE_NONE);
mSwipeRefreshLayoutList.setOnRefreshListener(this);
mSwipeRefreshLayoutEmpty.setOnRefreshListener(this);
final int color2resId = settings.getTheme() == Settings.Theme.LIGHT ?
R.color.holo_light_window_background : R.color.holo_dark_window_background;
mSwipeRefreshLayoutList.setColorScheme(R.color.holo_light_selected,
color2resId,
R.color.holo_light_selected,
color2resId);
mSwipeRefreshLayoutEmpty.setColorScheme(R.color.holo_light_selected,
color2resId,
R.color.holo_light_selected,
color2resId);
}
private void onFirstInvalidate() {
actionModeController.setListView(mListView);
mBrowser.invalidate();
}
@Override
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
saveManualState(outState);
}
@Override
protected void onVisible() {
if (mFirstRun || mRefreshFlag) {
onFirstInvalidate();
} else {
mParentOnNavigateListener.onNavigationCompleted(mBrowser
.getCurrentPath());
}
final AbstractBrowserActivity activity = (AbstractBrowserActivity) getActivity();
if (activity != null && !activity.isFinishing()) {
activity.setOnSequenceClickListener(mOnSequenceListener);
activity.setCurrentlyDisplayedFragment(this);
}
}
@Override
protected void onInvisible() {
if (actionModeController != null) {
actionModeController.finishActionMode();
}
if (mScannerTask != null
&& mScannerTask.getStatus() == AsyncTask.Status.RUNNING) {
mScannerTask.cancel(false);
}
}
@Override
public void onRefresh() {
if (mBrowser != null) {
mBrowser.invalidate();
}
}
private void startScan() {
if (mScannerTask != null
&& mScannerTask.getStatus() == AsyncTask.Status.RUNNING) {
mScannerTask.cancel(false);
}
final AbstractBrowserActivity activity = (AbstractBrowserActivity) getActivity();
if (activity != null) {
mScannerTask = new DirectoryScanTask(mBrowser,
activity.getGetContentMimeType(),
mAdapter,
Settings.getInstance(activity),
mSwipeRefreshLayoutList, mSwipeRefreshLayoutEmpty);
mScannerTask.execute(mBrowser.getCurrentPath());
}
}
@Nullable
public Browser getBrowser() {
return mBrowser;
}
private static final class SavedState implements Parcelable {
final int mPrevId;
final Parcelable mBrowserState;
SavedState(final int prevId, final Parcelable browserState) {
this.mPrevId = prevId;
this.mBrowserState = browserState;
}
SavedState(final Parcel source) {
this.mPrevId = source.readInt();
this.mBrowserState = source.readParcelable(Browser.class.getClassLoader());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.mPrevId);
dest.writeParcelable(this.mBrowserState, 0);
}
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
@NonNull
@Override
public SavedState createFromParcel(final Parcel source) {
return new SavedState(source);
}
@Override
public SavedState[] newArray(final int size) {
return new SavedState[size];
}
};
}
}