/*
* Copyright (C) 2014 Michell Bak
*
* 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.miz.mizuu.fragments;
import android.animation.Animator;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.support.v4.app.Fragment;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.melnykov.fab.FloatingActionButton;
import com.miz.abstractclasses.AbstractFileSourceBrowser;
import com.miz.db.DbAdapterSources;
import com.miz.filesources.BrowserFile;
import com.miz.filesources.BrowserSmb;
import com.miz.filesources.BrowserUpnp;
import com.miz.functions.BrowserFileObject;
import com.miz.functions.ContentItem;
import com.miz.functions.FileSource;
import com.miz.functions.MizLib;
import com.miz.functions.SimpleAnimatorListener;
import com.miz.mizuu.MizuuApplication;
import com.miz.mizuu.R;
import com.miz.service.WireUpnpService;
import com.miz.utils.ViewUtils;
import org.teleal.cling.android.AndroidUpnpService;
import org.teleal.cling.model.action.ActionInvocation;
import org.teleal.cling.model.message.UpnpResponse;
import org.teleal.cling.model.meta.Device;
import org.teleal.cling.model.meta.Service;
import org.teleal.cling.model.types.UDAServiceType;
import org.teleal.cling.support.contentdirectory.callback.Browse;
import org.teleal.cling.support.model.BrowseFlag;
import org.teleal.cling.support.model.DIDLContent;
import org.teleal.cling.support.model.SortCriterion;
import org.teleal.cling.support.model.container.Container;
import org.teleal.cling.support.model.item.Item;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import jcifs.smb.SmbFile;
import static com.miz.functions.MizLib.DOMAIN;
import static com.miz.functions.MizLib.MOVIE;
import static com.miz.functions.MizLib.PASSWORD;
import static com.miz.functions.MizLib.SERIAL_NUMBER;
import static com.miz.functions.MizLib.SERVER;
import static com.miz.functions.MizLib.TYPE;
import static com.miz.functions.MizLib.USER;
public class FileSourceBrowserFragment extends Fragment {
public static final int INITIALIZE = -2;
public static final int GO_BACK = -1;
private AbstractFileSourceBrowser<?> mBrowser;
private ListView mParent, mCurrent;
private View mProgress, mEmpty, mEmptyParent;
private int mType;
private BrowseFolder mBrowseFolder;
private ParentFolderAdapter mParentFolderAdapter;
private CurrentFolderAdapter mCurrentFolderAdapter;
private boolean mLoading = false, mIsMovie = false;
private AndroidUpnpService upnpService;
private ArrayAdapter<ContentItem> contentListAdapter;
private FloatingActionButton mFab;
/**
* Empty constructor as per the Fragment documentation
*/
public FileSourceBrowserFragment() {}
public static FileSourceBrowserFragment newInstanceFile(boolean isMovie) {
FileSourceBrowserFragment frag = new FileSourceBrowserFragment();
Bundle args = new Bundle();
args.putInt(TYPE, FileSource.FILE);
args.putBoolean(MOVIE, isMovie);
frag.setArguments(args);
return frag;
}
public static FileSourceBrowserFragment newInstanceSmbFile(String server, String user, String pass, String domain, boolean isMovie) {
FileSourceBrowserFragment frag = new FileSourceBrowserFragment();
Bundle args = new Bundle();
args.putInt(TYPE, FileSource.SMB);
args.putString(SERVER, server.replace("smb://", ""));
args.putString(USER, user);
args.putString(PASSWORD, pass);
args.putString(DOMAIN, domain);
args.putBoolean(MOVIE, isMovie);
frag.setArguments(args);
return frag;
}
public static FileSourceBrowserFragment newInstanceUpnp(String server, String serialNumber, boolean isMovie) {
FileSourceBrowserFragment frag = new FileSourceBrowserFragment();
Bundle args = new Bundle();
args.putInt(TYPE, FileSource.UPNP);
args.putString(SERIAL_NUMBER, serialNumber);
args.putString(SERVER, server);
args.putBoolean(MOVIE, isMovie);
frag.setArguments(args);
return frag;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mIsMovie = getArguments().getBoolean(MOVIE, false);
mType = getArguments().getInt(TYPE, FileSource.FILE);
if (mType == FileSource.UPNP) {
contentListAdapter = new ArrayAdapter<ContentItem>(getActivity(),
android.R.layout.simple_list_item_1);
}
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(mMessageReceiver, new IntentFilter("onBackPressed"));
}
private void showAlertDialog() {
AlertDialog.Builder ab = new AlertDialog.Builder(getActivity());
ab.setTitle(R.string.addCurrentFolderAsFileSource);
ab.setMessage(R.string.areYouSure);
ab.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
addFileSource();
dialog.dismiss();
}
});
ab.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
ab.show();
}
private void addFileSource() {
String contentType = mIsMovie ? DbAdapterSources.KEY_TYPE_MOVIE : DbAdapterSources.KEY_TYPE_SHOW;
DbAdapterSources dbHelper = MizuuApplication.getSourcesAdapter();
switch (mType) {
case FileSource.FILE:
dbHelper.createSource(mBrowser.getSubtitle(), contentType, FileSource.FILE, "", "", "");
break;
case FileSource.SMB:
dbHelper.createSource("smb://" + mBrowser.getSubtitle().replace("smb://", ""), contentType, FileSource.SMB, getArguments().getString(USER), getArguments().getString(PASSWORD), getArguments().getString(DOMAIN));
break;
case FileSource.UPNP:
try {
dbHelper.createSource("upnp://" + ((BrowserUpnp) mBrowser).getServer() + "/" + ((BrowserUpnp) mBrowser).getContainer().getTitle(), contentType, FileSource.UPNP, getArguments().getString(SERVER), getArguments().getString(SERIAL_NUMBER), ((BrowserUpnp) mBrowser).getContainer().getId());
} catch (Exception e) {
Toast.makeText(getActivity(), getString(R.string.errorSomethingWentWrong), Toast.LENGTH_SHORT).show();
}
break;
}
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(new Intent("mizuu-filesource-change"));
getActivity().finish();
}
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
onBackPressed();
}
};
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.filesource_browser, container, false);
}
@Override
public void onViewCreated(View v, Bundle savedInstanceState) {
super.onViewCreated(v, savedInstanceState);
mFab = (FloatingActionButton) v.findViewById(R.id.fab);
mFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewUtils.animateFabJump(v, new SimpleAnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
showAlertDialog();
}
});
}
});
mProgress = v.findViewById(R.id.progress);
mEmpty = v.findViewById(R.id.no_content);
mEmptyParent = v.findViewById(R.id.no_content_parent);
mParent = (ListView) v.findViewById(R.id.parentList);
mCurrent = (ListView) v.findViewById(R.id.currentList);
mCurrentFolderAdapter = new CurrentFolderAdapter();
mCurrent.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
if (arg2 == 0 && mType != FileSource.UPNP) {
// Go back
goBack();
} else {
if (mType != FileSource.UPNP) {
if (mBrowser.getBrowserFiles().get(arg2 - 1).isDirectory())
browse(arg2 - 1, false);
} else {
if (!mLoading) {
mLoading = true;
ContentItem content = contentListAdapter.getItem(arg2);
if (content.isContainer()) {
upnpService.getControlPoint().execute(new ContentBrowseCallback(getActivity(), content.getService(), content.getContainer(), contentListAdapter, true));
}
}
}
}
}
});
if (hasParentView()) {
mParentFolderAdapter = new ParentFolderAdapter();
mParent.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
browse(arg2, true);
}
});
}
browse(INITIALIZE, false);
}
private void loadFilesource() throws MalformedURLException, UnsupportedEncodingException {
switch (mType) {
case FileSource.FILE:
mBrowser = new BrowserFile(Environment.getExternalStorageDirectory());
break;
case FileSource.SMB:
mBrowser = new BrowserSmb(new SmbFile(MizLib.createSmbLoginString(
getArguments().getString(DOMAIN),
getArguments().getString(USER),
getArguments().getString(PASSWORD),
getArguments().getString(SERVER),
true)));
break;
case FileSource.UPNP:
if (mBrowser == null)
mBrowser = new BrowserUpnp(null, getArguments().getString(SERVER), getArguments().getString(SERIAL_NUMBER));
updateSubtitle();
break;
}
}
private class BrowseFolder extends AsyncTask<Void, Void, Boolean> {
private int mIndex;
private boolean mFromParent;
public BrowseFolder(int index, boolean fromParent) {
mIndex = index;
mFromParent = fromParent;
}
@Override
protected void onPreExecute() {
setLoading(true);
if (mType == FileSource.UPNP) {
getActivity().getApplicationContext().bindService(
new Intent(getActivity(), WireUpnpService.class), serviceConnection,
Context.BIND_AUTO_CREATE);
}
}
@Override
protected Boolean doInBackground(Void... params) {
boolean result = false;
if (mIndex == INITIALIZE) {
try {
loadFilesource();
} catch (Exception e) {}
} else if (mIndex == GO_BACK) {
result = mBrowser.goUp();
} else {
result = mBrowser.browse(mIndex, mFromParent);
}
return result;
}
@Override
protected void onPostExecute(Boolean result) {
if (!isAdded())
return;
if (mIndex >= GO_BACK && !result)
Toast.makeText(getActivity(), R.string.couldnt_load_folder, Toast.LENGTH_SHORT).show();
setLoading(false);
if (mCurrent.getAdapter() == null) {
if (mType != FileSource.UPNP)
mCurrent.setAdapter(mCurrentFolderAdapter);
else
mCurrent.setAdapter(contentListAdapter);
mCurrent.setEmptyView(mEmpty);
}
if (hasParentView())
if (mParent.getAdapter() == null) {
if (mType != FileSource.UPNP)
mParent.setAdapter(mParentFolderAdapter);
mParent.setEmptyView(mEmptyParent);
}
notifyDataSetChanged();
updateSubtitle();
mCurrent.setSelectionAfterHeaderView();
}
}
private void browse(int index, boolean fromParent) {
if (mLoading)
return;
if (mBrowseFolder != null)
mBrowseFolder.cancel(true);
mBrowseFolder = new BrowseFolder(index, fromParent);
mBrowseFolder.execute();
}
static class ViewHolder {
TextView name, size;
ImageView icon;
}
private class CurrentFolderAdapter extends BaseAdapter {
private List<BrowserFileObject> mItems = new ArrayList<BrowserFileObject>();
private LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@Override
public int getCount() {
return mItems.size() + 1;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public boolean isEmpty() {
return getCount() <= 1;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.file_list_item, parent, false);
holder = new ViewHolder();
holder.name = (TextView) convertView.findViewById(R.id.text1);
holder.size = (TextView) convertView.findViewById(R.id.size);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if (position == 0) {
holder.icon.setImageResource(R.drawable.ic_arrow_back_white_24dp);
holder.name.setText("...");
holder.size.setVisibility(View.GONE);
} else {
final BrowserFileObject browser = mItems.get(position - 1);
holder.name.setText(browser.getName());
if (browser.isDirectory()) {
holder.icon.setImageResource(R.drawable.ic_folder_open_white_24dp);
holder.size.setText("");
holder.size.setVisibility(View.GONE);
} else {
if (MizLib.isVideoFile(browser.getName()))
holder.icon.setImageResource(R.drawable.ic_movie_white_24dp);
else if (MizLib.isSubtitleFile(browser.getName()))
holder.icon.setImageResource(R.drawable.ic_subtitles_white_24dp);
else
holder.icon.setImageResource(R.drawable.ic_insert_drive_file_white_24dp);
holder.size.setText(MizLib.filesizeToString(browser.getSize()));
holder.size.setVisibility(View.VISIBLE);
}
}
return convertView;
}
@Override
public void notifyDataSetChanged() {
mItems = mBrowser.getBrowserFiles();
super.notifyDataSetChanged();
}
}
private class ParentFolderAdapter extends BaseAdapter {
private List<BrowserFileObject> mItems = new ArrayList<BrowserFileObject>();
private LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@Override
public int getCount() {
return mItems.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.file_list_item, parent, false);
holder = new ViewHolder();
holder.name = (TextView) convertView.findViewById(R.id.text1);
holder.size = (TextView) convertView.findViewById(R.id.size);
holder.size.setVisibility(View.GONE);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
holder.icon.setImageResource(R.drawable.ic_folder_open_white_24dp);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.name.setText(mItems.get(position).getName());
return convertView;
}
@Override
public void notifyDataSetChanged() {
mItems = mBrowser.getBrowserParentFiles();
super.notifyDataSetChanged();
}
}
private void onBackPressed() {
goBack();
}
private void goBack() {
if (mType != FileSource.UPNP)
browse(GO_BACK, false);
else {
if (mBrowser == null)
return;
Container parentContainer = new Container();
String id = "0";
if (((BrowserUpnp) mBrowser).getContainer() != null) {
id = ((BrowserUpnp) mBrowser).getParentId(((BrowserUpnp) mBrowser).getContainer().getId());
}
parentContainer.setId(id);
if (((BrowserUpnp) mBrowser).getService() != null)
upnpService.getControlPoint().execute(new ContentBrowseCallback(getActivity(), ((BrowserUpnp) mBrowser).getService(), parentContainer, contentListAdapter, false));
}
}
@Override
public void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mMessageReceiver);
if (mType == FileSource.UPNP)
getActivity().getApplicationContext().unbindService(serviceConnection);
}
private void setLoading(boolean loading) {
mLoading = loading;
if (loading) {
mProgress.setVisibility(View.VISIBLE);
mCurrent.setVisibility(View.GONE);
} else {
mProgress.setVisibility(View.GONE);
mCurrent.setVisibility(View.VISIBLE);
}
}
private boolean hasParentView() {
return mParent != null;
}
private void notifyDataSetChanged() {
if (mType != FileSource.UPNP) {
mCurrentFolderAdapter.notifyDataSetChanged();
if (hasParentView())
mParentFolderAdapter.notifyDataSetChanged();
} else {
contentListAdapter.notifyDataSetChanged();
}
}
private void updateSubtitle() {
((ActionBarActivity) getActivity()).getSupportActionBar().setSubtitle(mBrowser.getSubtitle());
}
private ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
upnpService = (AndroidUpnpService) service;
boolean found = false;
for (Device<?, ?, ?> device : upnpService.getRegistry().getDevices()) {
try {
if (device.getDetails().getSerialNumber() != null && !device.getDetails().getSerialNumber().isEmpty()) {
if (device.getDetails().getSerialNumber().equals(((BrowserUpnp) mBrowser).getSerial())) {
startBrowse(device);
found = true;
}
} else {
if (device.getIdentity().getUdn().toString().equals(((BrowserUpnp) mBrowser).getSerial())) {
startBrowse(device);
found = true;
}
}
} catch (Exception e) {}
}
if (!found)
Toast.makeText(getActivity(), getString(R.string.errorSomethingWentWrong), Toast.LENGTH_LONG).show();
}
public void onServiceDisconnected(ComponentName className) {
upnpService = null;
}
private void startBrowse(Device<?, ?, ?> device) {
Service<?, ?> service = device.findService(new UDAServiceType("ContentDirectory"));
((BrowserUpnp) mBrowser).setService(service);
Container rootContainer = new Container();
rootContainer.setId("0");
rootContainer.setTitle(device.getDetails().getFriendlyName());
upnpService.getControlPoint().execute(new ContentBrowseCallback(getActivity(), service, rootContainer, contentListAdapter, true));
}
};
private class ContentBrowseCallback extends Browse {
private Service<?, ?> service;
private ArrayAdapter<ContentItem> listAdapter;
private Activity activity;
private Container mContainer;
@SuppressWarnings("rawtypes")
public ContentBrowseCallback(Activity activity, Service service, Container container, ArrayAdapter<ContentItem> listadapter, boolean addToStack) {
super(service, container.getId(), BrowseFlag.DIRECT_CHILDREN, "*", 0, null, new SortCriterion(true, "dc:title"));
this.activity = activity;
this.service = service;
this.listAdapter = listadapter;
mContainer = container;
}
@SuppressWarnings("rawtypes")
public void received(final ActionInvocation actionInvocation, final DIDLContent didl) {
if (activity != null)
activity.runOnUiThread(new Runnable() {
public void run() {
try {
listAdapter.clear();
// Containers first
for (Container childContainer : didl.getContainers()) {
listAdapter.add(new ContentItem(childContainer, service));
}
// Now items
for (Item childItem : didl.getItems()) {
listAdapter.add(new ContentItem(childItem, service));
}
((BrowserUpnp) mBrowser).setContainer(mContainer);
((BrowserUpnp) mBrowser).addParentId(mContainer.getId(), mContainer.getParentID());
mLoading = false;
} catch (Exception ex) {
mLoading = false;
}
}
});
}
public void updateStatus(final Status status) {}
@SuppressWarnings("rawtypes")
@Override
public void failure(ActionInvocation invocation, final UpnpResponse operation, final String defaultMsg) {
if (defaultMsg != null && activity != null)
activity.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(activity, defaultMsg, Toast.LENGTH_LONG).show();
}
});
mLoading = false;
}
}
}