Move browsing functionality to the launch activity

The input for share URI has a drop-down list displaying discovered
shares.
This commit is contained in:
rthakohov 2017-09-02 21:20:11 -07:00
parent 751d538af4
commit 12eb8f2fad
12 changed files with 622 additions and 125 deletions

View file

@ -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 {

View file

@ -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);

View file

@ -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;
}
}
}

View file

@ -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));
}
}

View file

@ -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()) {

View file

@ -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;
}
}
}

View file

@ -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 {

View file

@ -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"/>
</vector>
<path
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>

View file

@ -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"/>

View 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>

View 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>

View file

@ -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>