commit
b1db7559d1
12 changed files with 622 additions and 125 deletions
|
@ -22,35 +22,85 @@ import android.os.AsyncTask;
|
|||
import android.util.Log;
|
||||
|
||||
import com.google.android.sambadocumentsprovider.TaskManager;
|
||||
import com.google.android.sambadocumentsprovider.base.DirectoryEntry;
|
||||
import com.google.android.sambadocumentsprovider.base.OnTaskFinishedCallback;
|
||||
import com.google.android.sambadocumentsprovider.browsing.broadcast.BroadcastBrowsingProvider;
|
||||
import com.google.android.sambadocumentsprovider.nativefacade.SmbClient;
|
||||
import com.google.android.sambadocumentsprovider.nativefacade.SmbDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* This class discovers Samba servers and shares under them available on the local network.
|
||||
*/
|
||||
public class NetworkBrowser {
|
||||
public static final Uri SMB_BROWSING_URI = Uri.parse("smb://");
|
||||
private static final Uri SMB_BROWSING_URI = Uri.parse("smb://");
|
||||
|
||||
private static final String TAG = "NetworkBrowser";
|
||||
|
||||
private final NetworkBrowsingProvider mMasterProvider;
|
||||
private final NetworkBrowsingProvider mBroadcastProvider;
|
||||
private final TaskManager mTaskManager;
|
||||
private final SmbClient mClient;
|
||||
|
||||
public NetworkBrowser(SmbClient client, TaskManager taskManager) {
|
||||
mMasterProvider = new MasterBrowsingProvider(client);
|
||||
mBroadcastProvider = new BroadcastBrowsingProvider();
|
||||
mTaskManager = taskManager;
|
||||
mClient = client;
|
||||
}
|
||||
|
||||
public AsyncTask getServersAsync(OnTaskFinishedCallback<List<String>> callback) {
|
||||
AsyncTask<Void, Void, List<String>> loadServersTask = new LoadServersTask(callback);
|
||||
/**
|
||||
* Asynchronously get available servers and shares under them.
|
||||
* A server name is mapped to the list of its children.
|
||||
*/
|
||||
public AsyncTask getSharesAsync(OnTaskFinishedCallback<Map<String, List<String>>> callback) {
|
||||
AsyncTask<Void, Void, Map<String, List<String>>> loadServersTask = new LoadServersTask(callback);
|
||||
|
||||
mTaskManager.runTask(SMB_BROWSING_URI, loadServersTask);
|
||||
|
||||
return loadServersTask;
|
||||
}
|
||||
|
||||
private Map<String, List<String>> getShares() throws BrowsingException {
|
||||
List<String> servers = getServers();
|
||||
|
||||
Map<String, List<String>> shares = new HashMap<>();
|
||||
|
||||
for (String server : servers) {
|
||||
try {
|
||||
shares.put(server, getSharesForServer(server));
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to load shares for server", e);
|
||||
}
|
||||
}
|
||||
|
||||
return shares;
|
||||
}
|
||||
|
||||
private List<String> getSharesForServer(String server) throws IOException {
|
||||
List<String> shares = new ArrayList<>();
|
||||
|
||||
String serverUri = SMB_BROWSING_URI + server;
|
||||
SmbDir serverDir = mClient.openDir(serverUri);
|
||||
|
||||
DirectoryEntry shareEntry;
|
||||
while ((shareEntry = serverDir.readDir()) != null) {
|
||||
if (shareEntry.getType() == DirectoryEntry.FILE_SHARE) {
|
||||
shares.add(serverUri + "/" + shareEntry.getName().trim());
|
||||
} else {
|
||||
Log.i(TAG, "Unsupported entry type: " + shareEntry.getType());
|
||||
}
|
||||
}
|
||||
|
||||
return shares;
|
||||
}
|
||||
|
||||
private List<String> getServers() throws BrowsingException {
|
||||
List<String> servers = null;
|
||||
|
||||
|
@ -67,23 +117,19 @@ public class NetworkBrowser {
|
|||
return servers;
|
||||
}
|
||||
|
||||
private class LoadServersTask extends AsyncTask<Void, Void, List<String>> {
|
||||
final OnTaskFinishedCallback<List<String>> mCallback;
|
||||
private class LoadServersTask extends AsyncTask<Void, Void, Map<String, List<String>>> {
|
||||
final OnTaskFinishedCallback<Map<String, List<String>>> mCallback;
|
||||
|
||||
private BrowsingException mException;
|
||||
|
||||
LoadServersTask(OnTaskFinishedCallback<List<String>> callback) {
|
||||
LoadServersTask(OnTaskFinishedCallback<Map<String, List<String>>> callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
List<String> loadData() throws BrowsingException {
|
||||
return getServers();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> doInBackground(Void... voids) {
|
||||
protected Map<String, List<String>> doInBackground(Void... voids) {
|
||||
try {
|
||||
return loadData();
|
||||
return getShares();
|
||||
} catch (BrowsingException e) {
|
||||
Log.e(TAG, "Failed to load data for network browsing: ", e);
|
||||
mException = e;
|
||||
|
@ -91,7 +137,7 @@ public class NetworkBrowser {
|
|||
}
|
||||
}
|
||||
|
||||
protected void onPostExecute(List<String> servers) {
|
||||
protected void onPostExecute(Map<String, List<String>> servers) {
|
||||
if (servers != null) {
|
||||
mCallback.onTaskFinished(OnTaskFinishedCallback.SUCCEEDED, servers, null);
|
||||
} else {
|
||||
|
|
|
@ -30,7 +30,9 @@ import java.net.InetAddress;
|
|||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
@ -57,12 +59,12 @@ public class BroadcastBrowsingProvider implements NetworkBrowsingProvider {
|
|||
serverFutures.add(mExecutor.submit(new GetServersForInterfaceTask(address, mTransId)));
|
||||
}
|
||||
|
||||
List<String> servers = new ArrayList<>();
|
||||
Set<String> servers = new HashSet<>();
|
||||
for (Future<List<String>> future : serverFutures) {
|
||||
servers.addAll(future.get());
|
||||
}
|
||||
|
||||
return servers;
|
||||
return new ArrayList<>(servers);
|
||||
} catch (IOException | ExecutionException | InterruptedException e) {
|
||||
Log.e(TAG, "Failed to get servers via broadcast", e);
|
||||
throw new BrowsingException("Failed to get servers via broadcast", e);
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.google.android.sambadocumentsprovider.mount;
|
||||
|
||||
import android.widget.Filter;
|
||||
import android.widget.Filterable;
|
||||
|
||||
import com.google.android.sambadocumentsprovider.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class BrowsingAutocompleteAdapter extends SectionedAdapter implements Filterable {
|
||||
private final List<ServerInfo> mServers = new ArrayList<>();
|
||||
|
||||
private List<ServerInfo> mFilteredServers = new ArrayList<>();
|
||||
|
||||
public void addServer(String name, List<String> children) {
|
||||
synchronized (mServers) {
|
||||
mServers.add(new ServerInfo(name, children));
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return new Filter() {
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence charSequence) {
|
||||
String query = charSequence == null ? "" : charSequence.toString().toLowerCase();
|
||||
|
||||
List<ServerInfo> filteredServers = new ArrayList<>();
|
||||
|
||||
if (!query.isEmpty()) {
|
||||
filteredServers.add(new ServerInfo(query, Collections.<String>emptyList()));
|
||||
}
|
||||
|
||||
if (!isLoadingData()) {
|
||||
synchronized (mServers) {
|
||||
for (ServerInfo serverInfo : mServers) {
|
||||
|
||||
List<String> displayedShares = new ArrayList<>();
|
||||
for (String share : serverInfo.getChildren()) {
|
||||
if (share.toLowerCase().contains(query)) {
|
||||
displayedShares.add(share);
|
||||
}
|
||||
}
|
||||
|
||||
if (!displayedShares.isEmpty()) {
|
||||
filteredServers.add(new ServerInfo(serverInfo.getName(), displayedShares));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FilterResults results = new FilterResults();
|
||||
results.values = filteredServers;
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
|
||||
mFilteredServers = (List<ServerInfo>) filterResults.values;
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSectionCount() {
|
||||
return mFilteredServers.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNameForSection(int sectionIndex) {
|
||||
return mFilteredServers.get(sectionIndex).getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconForSection(int sectionIndex) {
|
||||
return R.drawable.ic_server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSizeForSection(int sectionIndex) {
|
||||
return mFilteredServers.get(sectionIndex).getChildren().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNameForRow(int sectionIndex, int rowIndex) {
|
||||
return mFilteredServers.get(sectionIndex).getChildren().get(rowIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconForRow(int sectionIndex, int rowIndex) {
|
||||
return R.drawable.ic_folder_shared;
|
||||
}
|
||||
|
||||
private static class ServerInfo {
|
||||
private final String mName;
|
||||
private final List<String> mChildren;
|
||||
|
||||
ServerInfo(String name, List<String> children) {
|
||||
this.mName = name;
|
||||
this.mChildren = children;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
List<String> getChildren() {
|
||||
return mChildren;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.google.android.sambadocumentsprovider.mount;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class BrowsingAutocompleteTextView extends android.support.v7.widget.AppCompatAutoCompleteTextView {
|
||||
public BrowsingAutocompleteTextView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public BrowsingAutocompleteTextView(Context context, AttributeSet attributeSet) {
|
||||
super(context, attributeSet);
|
||||
}
|
||||
|
||||
public BrowsingAutocompleteTextView(Context context, AttributeSet attributeSet, int v) {
|
||||
super(context, attributeSet, v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enoughToFilter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void filter() {
|
||||
int length = getText().length();
|
||||
performFiltering(getText(), length == 0 ? 0 : getText().charAt(length - 1));
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ import android.view.KeyEvent;
|
|||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnKeyListener;
|
||||
|
@ -52,19 +53,20 @@ import com.google.android.sambadocumentsprovider.ShareManager;
|
|||
import com.google.android.sambadocumentsprovider.TaskManager;
|
||||
import com.google.android.sambadocumentsprovider.base.AuthFailedException;
|
||||
import com.google.android.sambadocumentsprovider.base.OnTaskFinishedCallback;
|
||||
import com.google.android.sambadocumentsprovider.browsing.NetworkBrowser;
|
||||
import com.google.android.sambadocumentsprovider.cache.DocumentCache;
|
||||
import com.google.android.sambadocumentsprovider.document.DocumentMetadata;
|
||||
import com.google.android.sambadocumentsprovider.nativefacade.SmbClient;
|
||||
import com.google.android.sambadocumentsprovider.provider.SambaDocumentsProvider;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MountServerActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = "MountServerActivity";
|
||||
|
||||
private static final String ACTION_BROWSE = "android.provider.action.BROWSE";
|
||||
|
||||
private static final String SHARE_PATH_KEY = "sharePath";
|
||||
private static final String NEEDS_PASSWORD_KEY = "needsPassword";
|
||||
private static final String DOMAIN_KEY = "domain";
|
||||
|
@ -100,15 +102,34 @@ public class MountServerActivity extends AppCompatActivity {
|
|||
}
|
||||
};
|
||||
|
||||
private final OnTaskFinishedCallback<Map<String, List<String>>> mBrowsingCallback
|
||||
= new OnTaskFinishedCallback<Map<String, List<String>>>() {
|
||||
@Override
|
||||
public void onTaskFinished(
|
||||
@Status int status, @Nullable Map<String, List<String>> result, @Nullable Exception exception) {
|
||||
|
||||
for (String server : result.keySet()) {
|
||||
mBrowsingAdapter.addServer(server, result.get(server));
|
||||
}
|
||||
|
||||
mBrowsingAdapter.finishLoading();
|
||||
|
||||
if (mSharePathEditText.isPopupShowing()) {
|
||||
mSharePathEditText.filter();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private DocumentCache mCache;
|
||||
private TaskManager mTaskManager;
|
||||
private ShareManager mShareManager;
|
||||
private SmbClient mClient;
|
||||
private BrowsingAutocompleteAdapter mBrowsingAdapter;
|
||||
|
||||
private CheckBox mNeedPasswordCheckbox;
|
||||
private View mPasswordHideGroup;
|
||||
|
||||
private EditText mSharePathEditText;
|
||||
private BrowsingAutocompleteTextView mSharePathEditText;
|
||||
private EditText mDomainEditText;
|
||||
private EditText mUsernameEditText;
|
||||
private EditText mPasswordEditText;
|
||||
|
@ -130,7 +151,7 @@ public class MountServerActivity extends AppCompatActivity {
|
|||
|
||||
mPasswordHideGroup = findViewById(R.id.password_hide_group);
|
||||
|
||||
mSharePathEditText = (EditText) findViewById(R.id.share_path);
|
||||
mSharePathEditText = (BrowsingAutocompleteTextView) findViewById(R.id.share_path);
|
||||
mSharePathEditText.setOnKeyListener(mMountKeyListener);
|
||||
|
||||
mUsernameEditText = (EditText) findViewById(R.id.username);
|
||||
|
@ -158,6 +179,8 @@ public class MountServerActivity extends AppCompatActivity {
|
|||
mConnectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
|
||||
|
||||
restoreSavedInstanceState(savedInstanceState);
|
||||
|
||||
startBrowsing();
|
||||
}
|
||||
|
||||
private void restoreSavedInstanceState(@Nullable Bundle savedInstanceState) {
|
||||
|
@ -220,6 +243,23 @@ public class MountServerActivity extends AppCompatActivity {
|
|||
}
|
||||
}
|
||||
|
||||
private void startBrowsing() {
|
||||
mSharePathEditText.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View view, MotionEvent motionEvent) {
|
||||
mSharePathEditText.filter();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mBrowsingAdapter = new BrowsingAutocompleteAdapter();
|
||||
mSharePathEditText.setAdapter(mBrowsingAdapter);
|
||||
mSharePathEditText.setThreshold(0);
|
||||
|
||||
NetworkBrowser browser = new NetworkBrowser(mClient, mTaskManager);
|
||||
browser.getSharesAsync(mBrowsingCallback);
|
||||
}
|
||||
|
||||
private void tryMount() {
|
||||
final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
|
||||
if (info == null || !info.isConnected()) {
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.google.android.sambadocumentsprovider.mount;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.sambadocumentsprovider.R;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class SectionedAdapter extends BaseAdapter {
|
||||
private static final String TAG = "SectionedAdapter";
|
||||
|
||||
@IntDef({SECTION_HEADER, REGULAR_ROW, LOADING_ROW})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface Type {}
|
||||
private static final int SECTION_HEADER = 0;
|
||||
private static final int REGULAR_ROW = 1;
|
||||
private static final int LOADING_ROW = 2;
|
||||
|
||||
private List<FlatRow> mFlatRows = new ArrayList<>();
|
||||
|
||||
private volatile boolean mIsLoadingData = true;
|
||||
|
||||
public void finishLoading() {
|
||||
mIsLoadingData = false;
|
||||
}
|
||||
|
||||
public abstract int getSectionCount();
|
||||
|
||||
public abstract String getNameForSection(int sectionIndex);
|
||||
public abstract int getIconForSection(int sectionIndex);
|
||||
public abstract int getSizeForSection(int sectionIndex);
|
||||
|
||||
public abstract String getNameForRow(int sectionIndex, int rowIndex);
|
||||
public abstract int getIconForRow(int sectionIndex, int rowIndex);
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mFlatRows.size() + (mIsLoadingData ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlatRow getItem(int position) {
|
||||
if (position == mFlatRows.size() && mIsLoadingData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mFlatRows.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
public @Type int getItemViewType(int position) {
|
||||
if (position == mFlatRows.size()) {
|
||||
return LOADING_ROW;
|
||||
}
|
||||
|
||||
return mFlatRows.get(position).isSectionHeader() ? SECTION_HEADER : REGULAR_ROW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View reuseView, ViewGroup parent) {
|
||||
int viewType = getItemViewType(position);
|
||||
|
||||
if (reuseView == null) {
|
||||
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||
|
||||
switch (viewType) {
|
||||
case LOADING_ROW:
|
||||
reuseView = inflater.inflate(R.layout.browsing_progress_item_layout, null);
|
||||
break;
|
||||
case SECTION_HEADER:
|
||||
case REGULAR_ROW:
|
||||
reuseView = inflater.inflate(R.layout.browsing_list_item_layout, null);
|
||||
break;
|
||||
default:
|
||||
// Should never happen.
|
||||
throw new RuntimeException("Unknown view type: " + viewType);
|
||||
}
|
||||
|
||||
reuseView.setTag(new ViewHolder(reuseView));
|
||||
}
|
||||
|
||||
ViewHolder holder = (ViewHolder) reuseView.getTag();
|
||||
|
||||
if (viewType == LOADING_ROW) {
|
||||
return reuseView;
|
||||
}
|
||||
|
||||
holder.bindView(mFlatRows.get(position));
|
||||
|
||||
return reuseView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
if (position == mFlatRows.size() && mIsLoadingData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !mFlatRows.get(position).isSectionHeader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDataSetChanged() {
|
||||
flattenData();
|
||||
|
||||
super.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void flattenData() {
|
||||
mFlatRows.clear();
|
||||
|
||||
int rowsCount = 0;
|
||||
|
||||
for (int i = 0; i < getSectionCount(); i++) {
|
||||
rowsCount = addFlatRow(getNameForSection(i), getIconForSection(i), true, rowsCount);
|
||||
|
||||
for (int j = 0; j < getSizeForSection(i); j++) {
|
||||
rowsCount = addFlatRow(getNameForRow(i, j), getIconForRow(i, j), false, rowsCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int addFlatRow(String name, int iconId, boolean isSectionHeader, int position) {
|
||||
FlatRow newRow = FlatRow.create(
|
||||
name,
|
||||
iconId,
|
||||
isSectionHeader,
|
||||
position < mFlatRows.size() ? mFlatRows.get(position) : null);
|
||||
|
||||
|
||||
if (position < mFlatRows.size()) {
|
||||
mFlatRows.set(position, newRow);
|
||||
} else {
|
||||
mFlatRows.add(newRow);
|
||||
}
|
||||
|
||||
return position + 1;
|
||||
}
|
||||
|
||||
protected boolean isLoadingData() {
|
||||
return mIsLoadingData;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
private ImageView mIconImageView;
|
||||
private TextView mNameTextView;
|
||||
|
||||
ViewHolder(View parent) {
|
||||
mIconImageView = parent.findViewById(R.id.browsing_icon);
|
||||
mNameTextView = parent.findViewById(R.id.browsing_name);
|
||||
}
|
||||
|
||||
void bindView(FlatRow row) {
|
||||
if (mIconImageView == null || mNameTextView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (row.isSectionHeader()) {
|
||||
mIconImageView.setPadding(8, 0, 0, 0);
|
||||
}
|
||||
|
||||
mIconImageView.setImageResource(row.getIconId());
|
||||
mNameTextView.setText(row.getName());
|
||||
|
||||
if (row.isSectionHeader()) {
|
||||
mIconImageView.setPadding(0, 0, 0, 0);
|
||||
mNameTextView.setTypeface(null, Typeface.BOLD);
|
||||
} else {
|
||||
mIconImageView.setPadding(16, 0, 0, 0);
|
||||
mNameTextView.setTypeface(null, Typeface.NORMAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class FlatRow {
|
||||
private String mName;
|
||||
private int mIconId;
|
||||
private boolean mIsSectionHeader;
|
||||
|
||||
private FlatRow(String name, int iconId, boolean isSectionHeader) {
|
||||
mName = name;
|
||||
mIconId = iconId;
|
||||
mIsSectionHeader = isSectionHeader;
|
||||
}
|
||||
|
||||
static FlatRow create(String name, int iconId, boolean isSectionHeader, FlatRow reuse) {
|
||||
if (reuse == null) {
|
||||
return new FlatRow(name, iconId, isSectionHeader);
|
||||
}
|
||||
|
||||
reuse.mName = name;
|
||||
reuse.mIconId = iconId;
|
||||
reuse.mIsSectionHeader = isSectionHeader;
|
||||
|
||||
return reuse;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
int getIconId() {
|
||||
return mIconId;
|
||||
}
|
||||
|
||||
boolean isSectionHeader() {
|
||||
return mIsSectionHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mName;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,7 +28,6 @@ import android.content.Context;
|
|||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
|
@ -59,7 +58,6 @@ import com.google.android.sambadocumentsprovider.document.LoadChildrenTask;
|
|||
import com.google.android.sambadocumentsprovider.base.OnTaskFinishedCallback;
|
||||
import com.google.android.sambadocumentsprovider.document.LoadDocumentTask;
|
||||
import com.google.android.sambadocumentsprovider.document.LoadStatTask;
|
||||
import com.google.android.sambadocumentsprovider.mount.MountServerActivity;
|
||||
import com.google.android.sambadocumentsprovider.nativefacade.SmbFacade;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
@ -131,22 +129,6 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
}
|
||||
};
|
||||
|
||||
private final OnTaskFinishedCallback<List<String>> mLoadSharesFinishedCallback =
|
||||
new OnTaskFinishedCallback<List<String>>() {
|
||||
@Override
|
||||
public void onTaskFinished(
|
||||
@OnTaskFinishedCallback.Status int status,
|
||||
@Nullable List<String> item,
|
||||
@Nullable Exception exception) {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Browsing callback");
|
||||
|
||||
mBrowsingStorage = item;
|
||||
|
||||
getContext().getContentResolver().notifyChange(
|
||||
toNotifyUri(toUri(NetworkBrowser.SMB_BROWSING_URI.toString())), null, false);
|
||||
}
|
||||
};
|
||||
|
||||
private final MountedShareChangeListener mShareChangeListener = new MountedShareChangeListener() {
|
||||
@Override
|
||||
public void onMountedServerChange() {
|
||||
|
@ -162,7 +144,6 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
private DocumentCache mCache;
|
||||
private TaskManager mTaskManager;
|
||||
private StorageManager mStorageManager;
|
||||
private NetworkBrowser mNetworkBrowser;
|
||||
|
||||
private List<String> mBrowsingStorage;
|
||||
|
||||
|
@ -178,7 +159,6 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
mShareManager = SambaProviderApplication.getServerManager(context);
|
||||
mShareManager.addListener(mShareChangeListener);
|
||||
mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
|
||||
mNetworkBrowser = SambaProviderApplication.getNetworkBrowser(context);
|
||||
|
||||
return mClient != null;
|
||||
}
|
||||
|
@ -190,14 +170,6 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
|
||||
MatrixCursor cursor = new MatrixCursor(projection);
|
||||
|
||||
cursor.addRow(new Object[] {
|
||||
NetworkBrowser.SMB_BROWSING_URI.toString(),
|
||||
NetworkBrowser.SMB_BROWSING_URI.toString(),
|
||||
getContext().getResources().getString(R.string.browsing_root_name),
|
||||
0,
|
||||
R.drawable.ic_cloud,
|
||||
});
|
||||
|
||||
for (String uri : mShareManager) {
|
||||
if (!mShareManager.isShareMounted(uri)) {
|
||||
continue;
|
||||
|
@ -253,12 +225,6 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
final MatrixCursor cursor = new MatrixCursor(projection);
|
||||
final Uri uri = toUri(documentId);
|
||||
|
||||
if (documentId.equals(NetworkBrowser.SMB_BROWSING_URI.toString())) {
|
||||
cursor.addRow(getCursorRowForBrowsingRoot(projection));
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
try {
|
||||
try (CacheResult result = mCache.get(uri)) {
|
||||
|
||||
|
@ -292,10 +258,6 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
if (BuildConfig.DEBUG) Log.d(TAG, "Querying children documents under " + documentId);
|
||||
projection = (projection == null) ? DEFAULT_DOCUMENT_PROJECTION : projection;
|
||||
|
||||
if (documentId.equals(NetworkBrowser.SMB_BROWSING_URI.toString())) {
|
||||
return getFilesSharesCursor(projection);
|
||||
}
|
||||
|
||||
final Uri uri = toUri(documentId);
|
||||
|
||||
try {
|
||||
|
@ -439,68 +401,6 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
return row;
|
||||
}
|
||||
|
||||
private Object[] getCursorRowForServer(
|
||||
String[] projection,
|
||||
String server) {
|
||||
Object[] row = new Object[projection.length];
|
||||
|
||||
for (int i = 0; i < projection.length; ++i) {
|
||||
switch (projection[i]) {
|
||||
case Document.COLUMN_DOCUMENT_ID:
|
||||
row[i] = NetworkBrowser.SMB_BROWSING_URI.toString() + server;
|
||||
break;
|
||||
case Document.COLUMN_DISPLAY_NAME:
|
||||
row[i] = server.isEmpty()
|
||||
? getContext().getResources().getString(R.string.browsing_root_name) : server;
|
||||
break;
|
||||
case Document.COLUMN_FLAGS:
|
||||
row[i] = 0;
|
||||
break;
|
||||
case Document.COLUMN_MIME_TYPE:
|
||||
row[i] = Document.MIME_TYPE_DIR;
|
||||
break;
|
||||
case Document.COLUMN_SIZE:
|
||||
case Document.COLUMN_LAST_MODIFIED:
|
||||
row[i] = null;
|
||||
break;
|
||||
case Document.COLUMN_ICON:
|
||||
row[i] = R.drawable.ic_server;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private Object[] getCursorRowForBrowsingRoot(String[] projection) {
|
||||
return getCursorRowForServer(projection, "");
|
||||
}
|
||||
|
||||
private Cursor getFilesSharesCursor(String[] projection) {
|
||||
final DocumentCursor cursor = new DocumentCursor(projection);
|
||||
|
||||
final Uri uri = toUri(NetworkBrowser.SMB_BROWSING_URI.toString());
|
||||
|
||||
if (mBrowsingStorage == null) {
|
||||
AsyncTask serversTask = mNetworkBrowser.getServersAsync(mLoadSharesFinishedCallback);
|
||||
|
||||
Bundle extra = new Bundle();
|
||||
extra.putBoolean(DocumentsContract.EXTRA_LOADING, true);
|
||||
|
||||
cursor.setNotificationUri(getContext().getContentResolver(), toNotifyUri(uri));
|
||||
cursor.setExtras(extra);
|
||||
cursor.setLoadingTask(serversTask);
|
||||
} else {
|
||||
for (String server : mBrowsingStorage) {
|
||||
cursor.addRow(getCursorRowForServer(projection, server));
|
||||
}
|
||||
|
||||
mBrowsingStorage = null;
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createDocument(String parentDocumentId, String mimeType, String displayName)
|
||||
throws FileNotFoundException {
|
||||
|
|
|
@ -21,8 +21,13 @@
|
|||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#646464"
|
||||
android:pathData="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36
|
||||
0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z"/>
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M20 5H4c-1.1 0-1.99 .9 -1.99 2L2 17c0 1.1 .9 2 2 2h16c1.1 0 2-.9
|
||||
2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2V8zm0 3h2v2h-2v-2zM8 8h2v2H8V8zm0
|
||||
3h2v2H8v-2zm-1 2H5v-2h2v2zm0-3H5V8h2v2zm9
|
||||
7H8v-2h8v2zm0-4h-2v-2h2v2zm0-3h-2V8h2v2zm3 3h-2v-2h2v2zm0-3h-2V8h2v2z" />
|
||||
<path
|
||||
android:pathData="M0 0h24v24H0zm0 0h24v24H0z" />
|
||||
</vector>
|
|
@ -40,7 +40,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText
|
||||
<com.google.android.sambadocumentsprovider.mount.BrowsingAutocompleteTextView
|
||||
android:id="@+id/share_path"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/clickable_height"
|
||||
|
@ -53,7 +53,6 @@
|
|||
android:id="@+id/needs_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/clickable_height"
|
||||
android:layout_below="@id/share_path"
|
||||
android:checked="false"
|
||||
android:text="@string/needs_password"/>
|
||||
|
||||
|
|
42
app/src/main/res/layout/browsing_list_item_layout.xml
Normal file
42
app/src/main/res/layout/browsing_list_item_layout.xml
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2017 Google Inc.
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/browsing_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_server"
|
||||
android:layout_marginStart="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/browsing_name"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
36
app/src/main/res/layout/browsing_progress_item_layout.xml
Normal file
36
app/src/main/res/layout/browsing_progress_item_layout.xml
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2017 Google Inc.
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/browsing_waiting_prompt"/>
|
||||
</LinearLayout>
|
|
@ -49,6 +49,7 @@
|
|||
<string name="no_web_browser">It needs a web browser to send feedback.</string>
|
||||
|
||||
<string name="browsing_root_name">Samba Shares</string>
|
||||
<string name="browsing_waiting_prompt">Looking for shares…</string>
|
||||
|
||||
<string name="pin_this_share">Pin this share</string>
|
||||
<string name="login">Login</string>
|
||||
|
|
Loading…
Reference in a new issue