Network browsing (#25)

Add basic network browsing support.

The embedded "master browser" functionality embedded in libsmbclient is the only way to browse as of now, but the code is structured to be extendable for other mechanisms of browsing.

This fixes #5.
This commit is contained in:
Ruslan Tkhakokhov 2017-07-25 18:04:54 -07:00 committed by Xu Tan
parent ae3b679a9c
commit f5fc69d1c2
13 changed files with 441 additions and 10 deletions

View file

@ -27,6 +27,7 @@ import android.net.NetworkRequest;
import android.util.Log; import android.util.Log;
import com.google.android.sambadocumentsprovider.SambaConfiguration.OnConfigurationChangedListener; import com.google.android.sambadocumentsprovider.SambaConfiguration.OnConfigurationChangedListener;
import com.google.android.sambadocumentsprovider.browsing.NetworkBrowser;
import com.google.android.sambadocumentsprovider.cache.DocumentCache; import com.google.android.sambadocumentsprovider.cache.DocumentCache;
import com.google.android.sambadocumentsprovider.nativefacade.CredentialCache; import com.google.android.sambadocumentsprovider.nativefacade.CredentialCache;
import com.google.android.sambadocumentsprovider.nativefacade.SambaMessageLooper; import com.google.android.sambadocumentsprovider.nativefacade.SambaMessageLooper;
@ -42,6 +43,7 @@ public class SambaProviderApplication extends Application {
private SmbFacade mSambaClient; private SmbFacade mSambaClient;
private ShareManager mShareManager; private ShareManager mShareManager;
private NetworkBrowser mNetworkBrowser;
@Override @Override
public void onCreate() { public void onCreate() {
@ -64,6 +66,8 @@ public class SambaProviderApplication extends Application {
mShareManager = new ShareManager(context, credentialCache); mShareManager = new ShareManager(context, credentialCache);
mNetworkBrowser = new NetworkBrowser(mSambaClient, mTaskManager);
registerNetworkCallback(context); registerNetworkCallback(context);
} }
@ -129,6 +133,10 @@ public class SambaProviderApplication extends Application {
return getApplication(context).mTaskManager; return getApplication(context).mTaskManager;
} }
public static NetworkBrowser getNetworkBrowser(Context context) {
return getApplication(context).mNetworkBrowser;
}
private static SambaProviderApplication getApplication(Context context) { private static SambaProviderApplication getApplication(Context context) {
return ((SambaProviderApplication) context.getApplicationContext()); return ((SambaProviderApplication) context.getApplicationContext());
} }

View file

@ -21,8 +21,6 @@ import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.AsyncTask.Status; import android.os.AsyncTask.Status;
import android.util.Log; import android.util.Log;
import com.google.android.sambadocumentsprovider.provider.ReadFileTask;
import com.google.android.sambadocumentsprovider.provider.WriteFileTask;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;

View file

@ -29,7 +29,7 @@ public class DirectoryEntry {
@IntDef({WORKGROUP, SERVER, FILE_SHARE, PRINTER_SHARE, COMMS_SHARE, IPC_SHARE, DIR, FILE, LINK}) @IntDef({WORKGROUP, SERVER, FILE_SHARE, PRINTER_SHARE, COMMS_SHARE, IPC_SHARE, DIR, FILE, LINK})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@interface Type {} public @interface Type {}
public static final int WORKGROUP = 1; public static final int WORKGROUP = 1;
public static final int SERVER = 2; public static final int SERVER = 2;
public static final int FILE_SHARE = 3; public static final int FILE_SHARE = 3;

View file

@ -0,0 +1,24 @@
/*
* 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.browsing;
class BrowsingException extends Exception {
BrowsingException(String message) {
super("Browsing failed: " + message);
}
}

View file

@ -0,0 +1,74 @@
/*
* 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.browsing;
import com.google.android.sambadocumentsprovider.base.DirectoryEntry;
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.List;
class MasterBrowsingProvider implements NetworkBrowsingProvider {
private static final String MASTER_BROWSING_DIR = "smb://";
private final SmbClient mClient;
MasterBrowsingProvider(SmbClient client) {
mClient = client;
}
@Override
public List<String> getServers() throws BrowsingException {
List<String> serversList = new ArrayList<>();
try {
SmbDir rootDir = mClient.openDir(MASTER_BROWSING_DIR);
List<DirectoryEntry> workgroups = getDirectoryChildren(rootDir);
for (DirectoryEntry workgroup : workgroups) {
if (workgroup.getType() == DirectoryEntry.WORKGROUP) {
List<DirectoryEntry> servers = getDirectoryChildren
(mClient.openDir(MASTER_BROWSING_DIR + workgroup.getName()));
for (DirectoryEntry server : servers) {
if (server.getType() == DirectoryEntry.SERVER) {
serversList.add(server.getName());
}
}
}
}
} catch (IOException e) {
throw new BrowsingException(e.getMessage());
}
return serversList;
}
private static List<DirectoryEntry> getDirectoryChildren(SmbDir dir) throws IOException {
List<DirectoryEntry> children = new ArrayList<>();
DirectoryEntry currentEntry;
while ((currentEntry = dir.readDir()) != null) {
children.add(currentEntry);
}
return children;
}
}

View file

@ -0,0 +1,96 @@
/*
* 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.browsing;
import android.net.Uri;
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.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;
import java.util.concurrent.Future;
public class NetworkBrowser {
public static final Uri SMB_BROWSING_URI = Uri.parse("smb://");
private static final String TAG = "NetworkBrowser";
private final NetworkBrowsingProvider mMasterProvider;
private final TaskManager mTaskManager;
private final Map<Uri, Future> mTasks = new HashMap<>();
public NetworkBrowser(SmbClient client, TaskManager taskManager) {
mMasterProvider = new MasterBrowsingProvider(client);
mTaskManager = taskManager;
}
public AsyncTask getServersAsync(OnTaskFinishedCallback<List<String>> callback) {
AsyncTask<Void, Void, List<String>> loadServersTask = new LoadServersTask(callback);
mTaskManager.runTask(SMB_BROWSING_URI, loadServersTask);
return loadServersTask;
}
private List<String> getServers() throws BrowsingException {
return mMasterProvider.getServers();
}
private class LoadServersTask extends AsyncTask<Void, Void, List<String>> {
final OnTaskFinishedCallback<List<String>> mCallback;
private BrowsingException mException;
LoadServersTask(OnTaskFinishedCallback<List<String>> callback) {
mCallback = callback;
}
List<String> loadData() throws BrowsingException {
return getServers();
}
@Override
protected List<String> doInBackground(Void... voids) {
try {
return loadData();
} catch (BrowsingException e) {
Log.e(TAG, "Failed to load data for network browsing: ", e);
mException = e;
return null;
}
}
protected void onPostExecute(List<String> servers) {
if (servers != null) {
mCallback.onTaskFinished(OnTaskFinishedCallback.SUCCEEDED, servers, null);
} else {
mCallback.onTaskFinished(OnTaskFinishedCallback.FAILED, null, mException);
}
}
}
}

View file

@ -0,0 +1,27 @@
/*
* 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.browsing;
import java.util.List;
interface NetworkBrowsingProvider {
/**
* Returns unresolved host names.
*/
List<String> getServers() throws BrowsingException;
}

View file

@ -25,6 +25,8 @@ import android.system.StructStat;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import com.google.android.sambadocumentsprovider.R;
import com.google.android.sambadocumentsprovider.base.DirectoryEntry; import com.google.android.sambadocumentsprovider.base.DirectoryEntry;
import com.google.android.sambadocumentsprovider.nativefacade.SmbClient; import com.google.android.sambadocumentsprovider.nativefacade.SmbClient;
import com.google.android.sambadocumentsprovider.nativefacade.SmbDir; import com.google.android.sambadocumentsprovider.nativefacade.SmbDir;
@ -32,7 +34,6 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -74,6 +75,18 @@ public class DocumentMetadata {
return mEntry.getType() == DirectoryEntry.FILE_SHARE; return mEntry.getType() == DirectoryEntry.FILE_SHARE;
} }
public Integer getIconResourceId() {
switch (mEntry.getType()) {
case DirectoryEntry.SERVER:
return R.drawable.ic_server;
case DirectoryEntry.FILE_SHARE:
return R.drawable.ic_folder_shared;
default:
// Tells SAF to use the default icon.
return null;
}
}
public Long getLastModified() { public Long getLastModified() {
final StructStat stat = mStat.get(); final StructStat stat = mStat.get();
return (stat == null) ? null : TimeUnit.MILLISECONDS.convert(stat.st_mtime, TimeUnit.SECONDS); return (stat == null) ? null : TimeUnit.MILLISECONDS.convert(stat.st_mtime, TimeUnit.SECONDS);
@ -293,6 +306,10 @@ public class DocumentMetadata {
return builder.build(); return builder.build();
} }
public static boolean isServerUri(Uri uri) {
return uri.getPathSegments().isEmpty() && !uri.getAuthority().isEmpty();
}
public static DocumentMetadata fromUri(Uri uri, SmbClient client) throws IOException { public static DocumentMetadata fromUri(Uri uri, SmbClient client) throws IOException {
final List<String> pathSegments = uri.getPathSegments(); final List<String> pathSegments = uri.getPathSegments();
if (pathSegments.isEmpty()) { if (pathSegments.isEmpty()) {
@ -315,9 +332,17 @@ public class DocumentMetadata {
return createShare(uri); return createShare(uri);
} }
public static DocumentMetadata createServer(Uri uri) {
return create(uri, DirectoryEntry.SERVER);
}
public static DocumentMetadata createShare(Uri uri) { public static DocumentMetadata createShare(Uri uri) {
return create(uri, DirectoryEntry.FILE_SHARE);
}
private static DocumentMetadata create(Uri uri, @DirectoryEntry.Type int type) {
final DirectoryEntry entry = final DirectoryEntry entry =
new DirectoryEntry(DirectoryEntry.FILE_SHARE, "", uri.getLastPathSegment()); new DirectoryEntry(type, "", uri.getLastPathSegment());
return new DocumentMetadata(uri, entry); return new DocumentMetadata(uri, entry);
} }
} }

View file

@ -56,6 +56,7 @@ import com.google.android.sambadocumentsprovider.cache.DocumentCache;
import com.google.android.sambadocumentsprovider.document.DocumentMetadata; import com.google.android.sambadocumentsprovider.document.DocumentMetadata;
import com.google.android.sambadocumentsprovider.nativefacade.SmbClient; import com.google.android.sambadocumentsprovider.nativefacade.SmbClient;
import com.google.android.sambadocumentsprovider.provider.SambaDocumentsProvider; import com.google.android.sambadocumentsprovider.provider.SambaDocumentsProvider;
import java.util.List; import java.util.List;
public class MountServerActivity extends AppCompatActivity { public class MountServerActivity extends AppCompatActivity {

View file

@ -27,6 +27,7 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.MatrixCursor; import android.database.MatrixCursor;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.CancellationSignal; import android.os.CancellationSignal;
@ -48,6 +49,7 @@ import com.google.android.sambadocumentsprovider.TaskManager;
import com.google.android.sambadocumentsprovider.base.AuthFailedException; import com.google.android.sambadocumentsprovider.base.AuthFailedException;
import com.google.android.sambadocumentsprovider.base.DirectoryEntry; import com.google.android.sambadocumentsprovider.base.DirectoryEntry;
import com.google.android.sambadocumentsprovider.base.DocumentCursor; import com.google.android.sambadocumentsprovider.base.DocumentCursor;
import com.google.android.sambadocumentsprovider.browsing.NetworkBrowser;
import com.google.android.sambadocumentsprovider.cache.CacheResult; import com.google.android.sambadocumentsprovider.cache.CacheResult;
import com.google.android.sambadocumentsprovider.cache.DocumentCache; import com.google.android.sambadocumentsprovider.cache.DocumentCache;
import com.google.android.sambadocumentsprovider.document.DocumentMetadata; import com.google.android.sambadocumentsprovider.document.DocumentMetadata;
@ -55,12 +57,11 @@ import com.google.android.sambadocumentsprovider.document.LoadChildrenTask;
import com.google.android.sambadocumentsprovider.base.OnTaskFinishedCallback; import com.google.android.sambadocumentsprovider.base.OnTaskFinishedCallback;
import com.google.android.sambadocumentsprovider.document.LoadDocumentTask; import com.google.android.sambadocumentsprovider.document.LoadDocumentTask;
import com.google.android.sambadocumentsprovider.document.LoadStatTask; import com.google.android.sambadocumentsprovider.document.LoadStatTask;
import com.google.android.sambadocumentsprovider.nativefacade.SmbClient;
import com.google.android.sambadocumentsprovider.nativefacade.SmbFacade; import com.google.android.sambadocumentsprovider.nativefacade.SmbFacade;
import com.google.android.sambadocumentsprovider.nativefacade.SmbFile;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -87,7 +88,8 @@ public class SambaDocumentsProvider extends DocumentsProvider {
Document.COLUMN_FLAGS, Document.COLUMN_FLAGS,
Document.COLUMN_MIME_TYPE, Document.COLUMN_MIME_TYPE,
Document.COLUMN_SIZE, Document.COLUMN_SIZE,
Document.COLUMN_LAST_MODIFIED Document.COLUMN_LAST_MODIFIED,
Document.COLUMN_ICON
}; };
private final OnTaskFinishedCallback<Uri> mLoadDocumentCallback = private final OnTaskFinishedCallback<Uri> mLoadDocumentCallback =
@ -127,6 +129,22 @@ 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() { private final MountedShareChangeListener mShareChangeListener = new MountedShareChangeListener() {
@Override @Override
public void onMountedServerChange() { public void onMountedServerChange() {
@ -142,6 +160,9 @@ public class SambaDocumentsProvider extends DocumentsProvider {
private DocumentCache mCache; private DocumentCache mCache;
private TaskManager mTaskManager; private TaskManager mTaskManager;
private StorageManager mStorageManager; private StorageManager mStorageManager;
private NetworkBrowser mNetworkBrowser;
private List<String> mBrowsingStorage = new ArrayList<>();
@Override @Override
public boolean onCreate() { public boolean onCreate() {
@ -155,6 +176,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
mShareManager = SambaProviderApplication.getServerManager(context); mShareManager = SambaProviderApplication.getServerManager(context);
mShareManager.addListener(mShareChangeListener); mShareManager.addListener(mShareChangeListener);
mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
mNetworkBrowser = SambaProviderApplication.getNetworkBrowser(context);
return mClient != null; return mClient != null;
} }
@ -165,6 +187,15 @@ public class SambaDocumentsProvider extends DocumentsProvider {
projection = (projection == null) ? DEFAULT_ROOT_PROJECTION : projection; projection = (projection == null) ? DEFAULT_ROOT_PROJECTION : projection;
MatrixCursor cursor = new MatrixCursor(projection, mShareManager.size()); MatrixCursor cursor = new MatrixCursor(projection, mShareManager.size());
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) { for (String uri : mShareManager) {
final String name; final String name;
final Uri parsedUri = Uri.parse(uri); final Uri parsedUri = Uri.parse(uri);
@ -204,7 +235,15 @@ public class SambaDocumentsProvider extends DocumentsProvider {
if (BuildConfig.DEBUG) Log.d(TAG, "Querying document: " + documentId); if (BuildConfig.DEBUG) Log.d(TAG, "Querying document: " + documentId);
projection = (projection == null) ? DEFAULT_DOCUMENT_PROJECTION : projection; projection = (projection == null) ? DEFAULT_DOCUMENT_PROJECTION : projection;
final MatrixCursor cursor = new MatrixCursor(projection);
final Uri uri = toUri(documentId); final Uri uri = toUri(documentId);
if (documentId.equals(NetworkBrowser.SMB_BROWSING_URI.toString())) {
cursor.addRow(getCursorRowForBrowsingRoot(projection));
return cursor;
}
try { try {
try (CacheResult result = mCache.get(uri)) { try (CacheResult result = mCache.get(uri)) {
@ -221,7 +260,6 @@ public class SambaDocumentsProvider extends DocumentsProvider {
metadata = result.getItem(); metadata = result.getItem();
} }
final MatrixCursor cursor = new MatrixCursor(projection);
cursor.addRow(getDocumentValues(projection, metadata)); cursor.addRow(getDocumentValues(projection, metadata));
return cursor; return cursor;
@ -239,13 +277,27 @@ public class SambaDocumentsProvider extends DocumentsProvider {
if (BuildConfig.DEBUG) Log.d(TAG, "Querying children documents under " + documentId); if (BuildConfig.DEBUG) Log.d(TAG, "Querying children documents under " + documentId);
projection = (projection == null) ? DEFAULT_DOCUMENT_PROJECTION : projection; projection = (projection == null) ? DEFAULT_DOCUMENT_PROJECTION : projection;
if (documentId.equals(NetworkBrowser.SMB_BROWSING_URI.toString())) {
return getFilesSharesCursor(projection);
}
final Uri uri = toUri(documentId); final Uri uri = toUri(documentId);
try { try {
if (DocumentMetadata.isServerUri(uri)) {
try (final CacheResult result = mCache.get(uri)) {
if (result.getState() == CacheResult.CACHE_MISS) {
DocumentMetadata metadata = DocumentMetadata.createServer(uri);
mCache.put(metadata);
}
}
}
try (final CacheResult result = mCache.get(uri)) { try (final CacheResult result = mCache.get(uri)) {
boolean isLoading = false; boolean isLoading = false;
final DocumentCursor cursor = new DocumentCursor(projection);
final Bundle extra = new Bundle(); final Bundle extra = new Bundle();
final Uri notifyUri = toNotifyUri(uri); final Uri notifyUri = toNotifyUri(uri);
final DocumentCursor cursor = new DocumentCursor(projection);
if (result.getState() == CacheResult.CACHE_MISS) { if (result.getState() == CacheResult.CACHE_MISS) {
// Last loading failed... Just feed the bitter fruit. // Last loading failed... Just feed the bitter fruit.
@ -359,11 +411,76 @@ public class SambaDocumentsProvider extends DocumentsProvider {
case Document.COLUMN_LAST_MODIFIED: case Document.COLUMN_LAST_MODIFIED:
row[i] = metadata.getLastModified(); row[i] = metadata.getLastModified();
break; break;
case Document.COLUMN_ICON:
row[i] = metadata.getIconResourceId();
break;
} }
} }
return row; 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.isEmpty()) {
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.clear();
}
return cursor;
}
@Override @Override
public String createDocument(String parentDocumentId, String mimeType, String displayName) public String createDocument(String parentDocumentId, String mimeType, String displayName)
throws FileNotFoundException { throws FileNotFoundException {

View file

@ -0,0 +1,28 @@
<?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/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
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>

View file

@ -0,0 +1,31 @@
<?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/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M0 0h24v24H0z"/>
<path
android:fillColor="#646464"
android:pathData="M20 18c1.1 0 1.99-.9 1.99-2L22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0
1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z"/>
</vector>

View file

@ -47,4 +47,6 @@
<string name="send_feedback">Send feedback</string> <string name="send_feedback">Send feedback</string>
<string name="no_web_browser">It needs a web browser to send feedback.</string> <string name="no_web_browser">It needs a web browser to send feedback.</string>
<string name="browsing_root_name">Samba Shares</string>
</resources> </resources>