Support network browsing via NetBIOS broadcast
If the master browsing approach fails, use NetBIOS broadcast to discover Samba servers as a fallback.
This commit is contained in:
parent
f5fc69d1c2
commit
65f6f67995
6 changed files with 430 additions and 108 deletions
|
@ -17,8 +17,12 @@
|
|||
|
||||
package com.google.android.sambadocumentsprovider.browsing;
|
||||
|
||||
class BrowsingException extends Exception {
|
||||
BrowsingException(String message) {
|
||||
public class BrowsingException extends Exception {
|
||||
public BrowsingException(String message) {
|
||||
super("Browsing failed: " + message);
|
||||
}
|
||||
|
||||
public BrowsingException(String message, Throwable cause) {
|
||||
super("Browsing failed: " + message, cause);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,30 +22,24 @@ 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;
|
||||
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<>();
|
||||
private final NetworkBrowsingProvider mMasterProvider;
|
||||
private final NetworkBrowsingProvider mBroadcastProvider;
|
||||
private final TaskManager mTaskManager;
|
||||
|
||||
public NetworkBrowser(SmbClient client, TaskManager taskManager) {
|
||||
mMasterProvider = new MasterBrowsingProvider(client);
|
||||
mBroadcastProvider = new BroadcastBrowsingProvider();
|
||||
mTaskManager = taskManager;
|
||||
}
|
||||
|
||||
|
@ -58,7 +52,19 @@ public class NetworkBrowser {
|
|||
}
|
||||
|
||||
private List<String> getServers() throws BrowsingException {
|
||||
return mMasterProvider.getServers();
|
||||
List<String> servers = null;
|
||||
|
||||
try {
|
||||
servers = mMasterProvider.getServers();
|
||||
} catch (BrowsingException e) {
|
||||
Log.e(TAG, "Master browsing failed", e);
|
||||
}
|
||||
|
||||
if (servers == null || servers.isEmpty()) {
|
||||
return mBroadcastProvider.getServers();
|
||||
}
|
||||
|
||||
return servers;
|
||||
}
|
||||
|
||||
private class LoadServersTask extends AsyncTask<Void, Void, List<String>> {
|
||||
|
|
|
@ -19,7 +19,7 @@ package com.google.android.sambadocumentsprovider.browsing;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
interface NetworkBrowsingProvider {
|
||||
public interface NetworkBrowsingProvider {
|
||||
/**
|
||||
* Returns unresolved host names.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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.broadcast;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.sambadocumentsprovider.BuildConfig;
|
||||
import com.google.android.sambadocumentsprovider.browsing.BrowsingException;
|
||||
import com.google.android.sambadocumentsprovider.browsing.NetworkBrowsingProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class BroadcastBrowsingProvider implements NetworkBrowsingProvider {
|
||||
private static final String TAG = "BroadcastBrowsing";
|
||||
private static final int LISTENING_TIMEOUT = 3000; // 3 seconds.
|
||||
private static final int NBT_PORT = 137;
|
||||
|
||||
private final ExecutorService mExecutor = Executors.newCachedThreadPool();
|
||||
|
||||
private int mTransId = 0;
|
||||
|
||||
@Override
|
||||
public List<String> getServers() throws BrowsingException {
|
||||
try {
|
||||
List<Future<List<String>>> serverFutures = new ArrayList<>();
|
||||
|
||||
List<String> broadcastAddresses = BroadcastUtils.getBroadcastAddress();
|
||||
for (String address : broadcastAddresses) {
|
||||
mTransId++;
|
||||
serverFutures.add(mExecutor.submit(new GetServersForInterfaceTask(address, mTransId)));
|
||||
}
|
||||
|
||||
List<String> servers = new ArrayList<>();
|
||||
for (Future<List<String>> future : serverFutures) {
|
||||
servers.addAll(future.get());
|
||||
}
|
||||
|
||||
return 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);
|
||||
}
|
||||
}
|
||||
|
||||
private class GetServersForInterfaceTask implements Callable<List<String>> {
|
||||
private final String mBroadcastAddress;
|
||||
private final int mTransId;
|
||||
|
||||
GetServersForInterfaceTask(String broadcastAddress, int transId) {
|
||||
mBroadcastAddress = broadcastAddress;
|
||||
mTransId = transId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> call() throws Exception {
|
||||
try (DatagramSocket socket = new DatagramSocket()) {
|
||||
InetAddress address = InetAddress.getByName(mBroadcastAddress);
|
||||
|
||||
sendNameQueryBroadcast(socket, address);
|
||||
|
||||
return listenForServers(socket);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendNameQueryBroadcast(
|
||||
DatagramSocket socket,
|
||||
InetAddress address) throws IOException {
|
||||
byte[] data = BroadcastUtils.createPacket(mTransId);
|
||||
int dataLength = data.length;
|
||||
|
||||
DatagramPacket packet = new DatagramPacket(data, 0, dataLength, address, NBT_PORT);
|
||||
socket.send(packet);
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Broadcast package sent");
|
||||
}
|
||||
|
||||
private List<String> listenForServers(DatagramSocket socket) throws IOException {
|
||||
List<String> servers = new ArrayList<>();
|
||||
|
||||
socket.setSoTimeout(LISTENING_TIMEOUT);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
byte[] buf = new byte[1024];
|
||||
DatagramPacket packet = new DatagramPacket(buf, buf.length);
|
||||
|
||||
socket.receive(packet);
|
||||
|
||||
try {
|
||||
servers.addAll(BroadcastUtils.extractServers(packet.getData(), mTransId));
|
||||
} catch (BrowsingException e) {
|
||||
Log.e(TAG, "Failed to parse incoming packet: ", e);
|
||||
}
|
||||
} catch (SocketTimeoutException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return servers;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* 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.broadcast;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.sambadocumentsprovider.BuildConfig;
|
||||
import com.google.android.sambadocumentsprovider.browsing.BrowsingException;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
class BroadcastUtils {
|
||||
private static final String TAG = "BroadcastUtils";
|
||||
|
||||
private static final int FILE_SERVER_NODE_TYPE = 0x20;
|
||||
private static final int SERVER_NAME_LENGTH = 15;
|
||||
private static final String SERVER_NAME_CHARSET = "US-ASCII";
|
||||
|
||||
/**
|
||||
* Generates a NetBIOS name query request.
|
||||
* https://tools.ietf.org/html/rfc1002
|
||||
* Section 4.2.12
|
||||
*/
|
||||
static byte[] createPacket(int transId) {
|
||||
ByteBuffer os = ByteBuffer.allocate(50);
|
||||
|
||||
char broadcastFlag = 0x0010;
|
||||
char questionCount = 1;
|
||||
char answerResourceCount = 0;
|
||||
char authorityResourceCount = 0;
|
||||
char additionalResourceCount = 0;
|
||||
|
||||
os.putChar((char) transId);
|
||||
os.putChar(broadcastFlag);
|
||||
os.putChar(questionCount);
|
||||
os.putChar(answerResourceCount);
|
||||
os.putChar(authorityResourceCount);
|
||||
os.putChar(additionalResourceCount);
|
||||
|
||||
// Length of name. 16 bytes of name encoded to 32 bytes.
|
||||
os.put((byte) 0x20);
|
||||
|
||||
// '*' character encodes to 2 bytes.
|
||||
os.put((byte) 0x43);
|
||||
os.put((byte) 0x4b);
|
||||
|
||||
// Write the remaining 15 nulls which encode to 30* 0x41
|
||||
for (int i = 0; i < 30; i++) {
|
||||
os.put((byte) 0x41);
|
||||
}
|
||||
|
||||
// Length of next segment.
|
||||
os.put((byte) 0);
|
||||
|
||||
// Question type: Node status
|
||||
os.putChar((char) 0x21);
|
||||
|
||||
// Question class: Internet
|
||||
os.putChar((char) 0x01);
|
||||
|
||||
return os.array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a positive response to NetBIOS name request query.
|
||||
* https://tools.ietf.org/html/rfc1002
|
||||
* Section 4.2.13
|
||||
*/
|
||||
static List<String> extractServers(byte[] data, int expectedTransId) throws BrowsingException {
|
||||
try {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
int transId = buffer.getChar();
|
||||
if (transId != expectedTransId) {
|
||||
// This response is not to our broadcast.
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Irrelevant broadcast response");
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
skipBytes(buffer, 2); // Skip flags.
|
||||
|
||||
skipBytes(buffer, 2); // No questions.
|
||||
skipBytes(buffer, 2); // Skip answers count.
|
||||
skipBytes(buffer, 2); // No authority resources.
|
||||
skipBytes(buffer, 2); // No additional resources.
|
||||
|
||||
int nameLength = buffer.get();
|
||||
skipBytes(buffer, nameLength);
|
||||
|
||||
skipBytes(buffer, 1);
|
||||
|
||||
int nodeStatus = buffer.getChar();
|
||||
if (nodeStatus != 0x20 && nodeStatus != 0x21) {
|
||||
throw new BrowsingException("Received negative response for the broadcast");
|
||||
}
|
||||
|
||||
skipBytes(buffer, 2);
|
||||
skipBytes(buffer, 4);
|
||||
skipBytes(buffer, 2);
|
||||
|
||||
int addressListEntryCount = buffer.get();
|
||||
|
||||
List<String> servers = new ArrayList<>();
|
||||
for (int i = 0; i < addressListEntryCount; i++) {
|
||||
byte[] nameArray = new byte[SERVER_NAME_LENGTH];
|
||||
buffer.get(nameArray, 0, SERVER_NAME_LENGTH);
|
||||
|
||||
final String serverName = new String(nameArray, Charset.forName(SERVER_NAME_CHARSET));
|
||||
final int type = buffer.get();
|
||||
|
||||
if (type == FILE_SERVER_NODE_TYPE) {
|
||||
servers.add(serverName);
|
||||
}
|
||||
|
||||
skipBytes(buffer, 2);
|
||||
}
|
||||
|
||||
return servers;
|
||||
} catch (BufferUnderflowException e) {
|
||||
Log.e(TAG, "Malformed incoming packet");
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
static List<String> getBroadcastAddress() throws BrowsingException, SocketException {
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
|
||||
List<String> broadcastAddresses = new ArrayList<>();
|
||||
|
||||
while (interfaces.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = interfaces.nextElement();
|
||||
if (networkInterface.isLoopback()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (InterfaceAddress interfaceAddress :
|
||||
networkInterface.getInterfaceAddresses()) {
|
||||
InetAddress broadcast = interfaceAddress.getBroadcast();
|
||||
|
||||
if (broadcast != null) {
|
||||
broadcastAddresses.add(broadcast.toString().substring(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return broadcastAddresses;
|
||||
}
|
||||
|
||||
private static void skipBytes(ByteBuffer buffer, int bytes) {
|
||||
for (int i = 0; i < bytes; i++) {
|
||||
buffer.get();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -75,75 +75,75 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
private static final String TAG = "SambaDocumentsProvider";
|
||||
|
||||
private static final String[] DEFAULT_ROOT_PROJECTION = {
|
||||
Root.COLUMN_ROOT_ID,
|
||||
Root.COLUMN_DOCUMENT_ID,
|
||||
Root.COLUMN_TITLE,
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.COLUMN_ICON
|
||||
Root.COLUMN_ROOT_ID,
|
||||
Root.COLUMN_DOCUMENT_ID,
|
||||
Root.COLUMN_TITLE,
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.COLUMN_ICON
|
||||
};
|
||||
|
||||
private static final String[] DEFAULT_DOCUMENT_PROJECTION = {
|
||||
Document.COLUMN_DOCUMENT_ID,
|
||||
Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_FLAGS,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_SIZE,
|
||||
Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_ICON
|
||||
Document.COLUMN_DOCUMENT_ID,
|
||||
Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_FLAGS,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_SIZE,
|
||||
Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_ICON
|
||||
};
|
||||
|
||||
private final OnTaskFinishedCallback<Uri> mLoadDocumentCallback =
|
||||
new OnTaskFinishedCallback<Uri>() {
|
||||
@Override
|
||||
public void onTaskFinished(@Status int status, @Nullable Uri uri, Exception exception) {
|
||||
getContext().getContentResolver().notifyChange(toNotifyUri(uri), null, false);
|
||||
}
|
||||
};
|
||||
new OnTaskFinishedCallback<Uri>() {
|
||||
@Override
|
||||
public void onTaskFinished(@Status int status, @Nullable Uri uri, Exception exception) {
|
||||
getContext().getContentResolver().notifyChange(toNotifyUri(uri), null, false);
|
||||
}
|
||||
};
|
||||
|
||||
private final OnTaskFinishedCallback<DocumentMetadata> mLoadChildrenCallback =
|
||||
new OnTaskFinishedCallback<DocumentMetadata>() {
|
||||
@Override
|
||||
public void onTaskFinished(@Status int status, DocumentMetadata metadata,
|
||||
Exception exception) {
|
||||
// Notify remote side that we get the list even though we don't have the stat yet.
|
||||
// If it failed we still should notify the remote side that the loading failed.
|
||||
getContext().getContentResolver().notifyChange(
|
||||
toNotifyUri(metadata.getUri()), null, false);
|
||||
}
|
||||
};
|
||||
new OnTaskFinishedCallback<DocumentMetadata>() {
|
||||
@Override
|
||||
public void onTaskFinished(@Status int status, DocumentMetadata metadata,
|
||||
Exception exception) {
|
||||
// Notify remote side that we get the list even though we don't have the stat yet.
|
||||
// If it failed we still should notify the remote side that the loading failed.
|
||||
getContext().getContentResolver().notifyChange(
|
||||
toNotifyUri(metadata.getUri()), null, false);
|
||||
}
|
||||
};
|
||||
|
||||
private final OnTaskFinishedCallback<String> mWriteFinishedCallback =
|
||||
new OnTaskFinishedCallback<String>() {
|
||||
@Override
|
||||
public void onTaskFinished(
|
||||
@Status int status, @Nullable String item, Exception exception) {
|
||||
final Uri uri = toUri(item);
|
||||
try (final CacheResult result = mCache.get(uri)) {
|
||||
if (result.getState() != CacheResult.CACHE_MISS) {
|
||||
result.getItem().reset();
|
||||
}
|
||||
}
|
||||
new OnTaskFinishedCallback<String>() {
|
||||
@Override
|
||||
public void onTaskFinished(
|
||||
@Status int status, @Nullable String item, Exception exception) {
|
||||
final Uri uri = toUri(item);
|
||||
try (final CacheResult result = mCache.get(uri)) {
|
||||
if (result.getState() != CacheResult.CACHE_MISS) {
|
||||
result.getItem().reset();
|
||||
}
|
||||
}
|
||||
|
||||
final Uri parentUri = DocumentMetadata.buildParentUri(uri);
|
||||
getContext().getContentResolver().notifyChange(toNotifyUri(parentUri), null, false);
|
||||
}
|
||||
};
|
||||
final Uri parentUri = DocumentMetadata.buildParentUri(uri);
|
||||
getContext().getContentResolver().notifyChange(toNotifyUri(parentUri), null, false);
|
||||
}
|
||||
};
|
||||
|
||||
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");
|
||||
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;
|
||||
mBrowsingStorage = item;
|
||||
|
||||
getContext().getContentResolver().notifyChange(
|
||||
toNotifyUri(toUri(NetworkBrowser.SMB_BROWSING_URI.toString())), null, false);
|
||||
}
|
||||
};
|
||||
getContext().getContentResolver().notifyChange(
|
||||
toNotifyUri(toUri(NetworkBrowser.SMB_BROWSING_URI.toString())), null, false);
|
||||
}
|
||||
};
|
||||
|
||||
private final MountedShareChangeListener mShareChangeListener = new MountedShareChangeListener() {
|
||||
@Override
|
||||
|
@ -162,7 +162,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
private StorageManager mStorageManager;
|
||||
private NetworkBrowser mNetworkBrowser;
|
||||
|
||||
private List<String> mBrowsingStorage = new ArrayList<>();
|
||||
private List<String> mBrowsingStorage;
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
|
@ -189,11 +189,11 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
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,
|
||||
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) {
|
||||
|
@ -211,11 +211,11 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
name = metadata.getDisplayName();
|
||||
|
||||
cursor.addRow(new Object[] {
|
||||
toRootId(metadata),
|
||||
toDocumentId(parsedUri),
|
||||
name,
|
||||
Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD,
|
||||
R.drawable.ic_folder_shared
|
||||
toRootId(metadata),
|
||||
toDocumentId(parsedUri),
|
||||
name,
|
||||
Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD,
|
||||
R.drawable.ic_folder_shared
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -273,7 +273,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder)
|
||||
throws FileNotFoundException {
|
||||
throws FileNotFoundException {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Querying children documents under " + documentId);
|
||||
projection = (projection == null) ? DEFAULT_DOCUMENT_PROJECTION : projection;
|
||||
|
||||
|
@ -304,7 +304,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
mCache.throwLastExceptionIfAny(uri);
|
||||
|
||||
final LoadDocumentTask task =
|
||||
new LoadDocumentTask(uri, mClient, mCache, mLoadDocumentCallback);
|
||||
new LoadDocumentTask(uri, mClient, mCache, mLoadDocumentCallback);
|
||||
mTaskManager.runTask(uri, task);
|
||||
cursor.setLoadingTask(task);
|
||||
|
||||
|
@ -321,7 +321,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
final Map<Uri, DocumentMetadata> childrenMap = metadata.getChildren();
|
||||
if (childrenMap == null || result.getState() == CacheResult.CACHE_EXPIRED) {
|
||||
final LoadChildrenTask task =
|
||||
new LoadChildrenTask(metadata, mClient, mCache, mLoadChildrenCallback);
|
||||
new LoadChildrenTask(metadata, mClient, mCache, mLoadChildrenCallback);
|
||||
mTaskManager.runTask(uri, task);
|
||||
cursor.setLoadingTask(task);
|
||||
|
||||
|
@ -340,13 +340,13 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
}
|
||||
if (!isLoading && !docMap.isEmpty()) {
|
||||
LoadStatTask task = new LoadStatTask(docMap, mClient,
|
||||
new OnTaskFinishedCallback<Map<Uri, DocumentMetadata>>() {
|
||||
@Override
|
||||
public void onTaskFinished(
|
||||
@Status int status, Map<Uri, DocumentMetadata> item, Exception exception) {
|
||||
getContext().getContentResolver().notifyChange(notifyUri, null, false);
|
||||
}
|
||||
});
|
||||
new OnTaskFinishedCallback<Map<Uri, DocumentMetadata>>() {
|
||||
@Override
|
||||
public void onTaskFinished(
|
||||
@Status int status, Map<Uri, DocumentMetadata> item, Exception exception) {
|
||||
getContext().getContentResolver().notifyChange(notifyUri, null, false);
|
||||
}
|
||||
});
|
||||
mTaskManager.runTask(uri, task);
|
||||
cursor.setLoadingTask(task);
|
||||
|
||||
|
@ -381,7 +381,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
}
|
||||
|
||||
private Object[] getDocumentValues(
|
||||
String[] projection, DocumentMetadata metadata) {
|
||||
String[] projection, DocumentMetadata metadata) {
|
||||
Object[] row = new Object[projection.length];
|
||||
for (int i = 0; i < projection.length; ++i) {
|
||||
switch (projection[i]) {
|
||||
|
@ -461,7 +461,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
|
||||
final Uri uri = toUri(NetworkBrowser.SMB_BROWSING_URI.toString());
|
||||
|
||||
if (mBrowsingStorage.isEmpty()) {
|
||||
if (mBrowsingStorage == null) {
|
||||
AsyncTask serversTask = mNetworkBrowser.getServersAsync(mLoadSharesFinishedCallback);
|
||||
|
||||
Bundle extra = new Bundle();
|
||||
|
@ -475,7 +475,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
cursor.addRow(getCursorRowForServer(projection, server));
|
||||
}
|
||||
|
||||
mBrowsingStorage.clear();
|
||||
mBrowsingStorage = null;
|
||||
}
|
||||
|
||||
return cursor;
|
||||
|
@ -483,15 +483,15 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
|
||||
@Override
|
||||
public String createDocument(String parentDocumentId, String mimeType, String displayName)
|
||||
throws FileNotFoundException {
|
||||
throws FileNotFoundException {
|
||||
try {
|
||||
final Uri parentUri = toUri(parentDocumentId);
|
||||
|
||||
boolean isDir = Document.MIME_TYPE_DIR.equals(mimeType);
|
||||
final DirectoryEntry entry = new DirectoryEntry(
|
||||
isDir ? DirectoryEntry.DIR : DirectoryEntry.FILE,
|
||||
"", // comment
|
||||
displayName);
|
||||
isDir ? DirectoryEntry.DIR : DirectoryEntry.FILE,
|
||||
"", // comment
|
||||
displayName);
|
||||
final Uri uri = DocumentMetadata.buildChildUri(parentUri, entry);
|
||||
|
||||
if (isDir) {
|
||||
|
@ -617,15 +617,15 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
|
||||
@Override
|
||||
public void removeDocument(String documentId, String parentDocumentId)
|
||||
throws FileNotFoundException {
|
||||
throws FileNotFoundException {
|
||||
// documentId is hierarchical. It can only have one parent.
|
||||
deleteDocument(documentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String moveDocument(
|
||||
String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId)
|
||||
throws FileNotFoundException {
|
||||
String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId)
|
||||
throws FileNotFoundException {
|
||||
try {
|
||||
final Uri uri = toUri(sourceDocumentId);
|
||||
final Uri targetParentUri = toUri(targetParentDocumentId);
|
||||
|
@ -637,19 +637,19 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
final List<String> pathSegmentsOfSource = uri.getPathSegments();
|
||||
final List<String> pathSegmentsOfTargetParent = targetParentUri.getPathSegments();
|
||||
if (pathSegmentsOfSource.isEmpty() ||
|
||||
pathSegmentsOfTargetParent.isEmpty() ||
|
||||
!Objects.equals(pathSegmentsOfSource.get(0), pathSegmentsOfTargetParent.get(0))) {
|
||||
pathSegmentsOfTargetParent.isEmpty() ||
|
||||
!Objects.equals(pathSegmentsOfSource.get(0), pathSegmentsOfTargetParent.get(0))) {
|
||||
throw new UnsupportedOperationException("Instance move across shares are not supported.");
|
||||
}
|
||||
|
||||
final Uri targetUri = DocumentMetadata
|
||||
.buildChildUri(targetParentUri, uri.getLastPathSegment());
|
||||
.buildChildUri(targetParentUri, uri.getLastPathSegment());
|
||||
mClient.rename(uri.toString(), targetUri.toString());
|
||||
|
||||
revokeDocumentPermission(sourceDocumentId);
|
||||
|
||||
getContext().getContentResolver()
|
||||
.notifyChange(toNotifyUri(DocumentMetadata.buildParentUri(uri)), null, false);
|
||||
.notifyChange(toNotifyUri(DocumentMetadata.buildParentUri(uri)), null, false);
|
||||
getContext().getContentResolver().notifyChange(toNotifyUri(targetParentUri), null, false);
|
||||
|
||||
try (CacheResult result = mCache.get(uri)) {
|
||||
|
@ -672,7 +672,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(String documentId, String mode,
|
||||
CancellationSignal cancellationSignal) throws FileNotFoundException {
|
||||
CancellationSignal cancellationSignal) throws FileNotFoundException {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "Opening document " + documentId + " with mode " + mode);
|
||||
|
||||
try {
|
||||
|
@ -680,7 +680,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
OnTaskFinishedCallback<String> callback =
|
||||
mode.contains("w") ? mWriteFinishedCallback : null;
|
||||
mode.contains("w") ? mWriteFinishedCallback : null;
|
||||
return mClient.openProxyFile(
|
||||
uri,
|
||||
mode,
|
||||
|
@ -709,13 +709,13 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
|||
switch (mode) {
|
||||
case "r": {
|
||||
final ReadFileTask task = new ReadFileTask(
|
||||
uri, mClient, pipe[1], mBufferPool);
|
||||
uri, mClient, pipe[1], mBufferPool);
|
||||
mTaskManager.runIoTask(task);
|
||||
}
|
||||
return pipe[0];
|
||||
case "w": {
|
||||
final WriteFileTask task =
|
||||
new WriteFileTask(uri, mClient, pipe[0], mBufferPool, mWriteFinishedCallback);
|
||||
new WriteFileTask(uri, mClient, pipe[0], mBufferPool, mWriteFinishedCallback);
|
||||
mTaskManager.runIoTask(task);
|
||||
return pipe[1];
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue