Merge pull request #28 from rthakohov/network-browsing-broadcast
Network browsing via broadcasting
This commit is contained in:
commit
baf3ba19f4
6 changed files with 430 additions and 108 deletions
|
@ -17,8 +17,12 @@
|
||||||
|
|
||||||
package com.google.android.sambadocumentsprovider.browsing;
|
package com.google.android.sambadocumentsprovider.browsing;
|
||||||
|
|
||||||
class BrowsingException extends Exception {
|
public class BrowsingException extends Exception {
|
||||||
BrowsingException(String message) {
|
public BrowsingException(String message) {
|
||||||
super("Browsing failed: " + 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 android.util.Log;
|
||||||
|
|
||||||
import com.google.android.sambadocumentsprovider.TaskManager;
|
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.base.OnTaskFinishedCallback;
|
||||||
|
import com.google.android.sambadocumentsprovider.browsing.broadcast.BroadcastBrowsingProvider;
|
||||||
import com.google.android.sambadocumentsprovider.nativefacade.SmbClient;
|
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.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
|
|
||||||
public class NetworkBrowser {
|
public class NetworkBrowser {
|
||||||
public static final Uri SMB_BROWSING_URI = Uri.parse("smb://");
|
public static final Uri SMB_BROWSING_URI = Uri.parse("smb://");
|
||||||
|
|
||||||
private static final String TAG = "NetworkBrowser";
|
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) {
|
public NetworkBrowser(SmbClient client, TaskManager taskManager) {
|
||||||
mMasterProvider = new MasterBrowsingProvider(client);
|
mMasterProvider = new MasterBrowsingProvider(client);
|
||||||
|
mBroadcastProvider = new BroadcastBrowsingProvider();
|
||||||
mTaskManager = taskManager;
|
mTaskManager = taskManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +52,19 @@ public class NetworkBrowser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getServers() throws BrowsingException {
|
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>> {
|
private class LoadServersTask extends AsyncTask<Void, Void, List<String>> {
|
||||||
|
|
|
@ -19,7 +19,7 @@ package com.google.android.sambadocumentsprovider.browsing;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
interface NetworkBrowsingProvider {
|
public interface NetworkBrowsingProvider {
|
||||||
/**
|
/**
|
||||||
* Returns unresolved host names.
|
* 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 TAG = "SambaDocumentsProvider";
|
||||||
|
|
||||||
private static final String[] DEFAULT_ROOT_PROJECTION = {
|
private static final String[] DEFAULT_ROOT_PROJECTION = {
|
||||||
Root.COLUMN_ROOT_ID,
|
Root.COLUMN_ROOT_ID,
|
||||||
Root.COLUMN_DOCUMENT_ID,
|
Root.COLUMN_DOCUMENT_ID,
|
||||||
Root.COLUMN_TITLE,
|
Root.COLUMN_TITLE,
|
||||||
Root.COLUMN_FLAGS,
|
Root.COLUMN_FLAGS,
|
||||||
Root.COLUMN_ICON
|
Root.COLUMN_ICON
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final String[] DEFAULT_DOCUMENT_PROJECTION = {
|
private static final String[] DEFAULT_DOCUMENT_PROJECTION = {
|
||||||
Document.COLUMN_DOCUMENT_ID,
|
Document.COLUMN_DOCUMENT_ID,
|
||||||
Document.COLUMN_DISPLAY_NAME,
|
Document.COLUMN_DISPLAY_NAME,
|
||||||
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
|
Document.COLUMN_ICON
|
||||||
};
|
};
|
||||||
|
|
||||||
private final OnTaskFinishedCallback<Uri> mLoadDocumentCallback =
|
private final OnTaskFinishedCallback<Uri> mLoadDocumentCallback =
|
||||||
new OnTaskFinishedCallback<Uri>() {
|
new OnTaskFinishedCallback<Uri>() {
|
||||||
@Override
|
@Override
|
||||||
public void onTaskFinished(@Status int status, @Nullable Uri uri, Exception exception) {
|
public void onTaskFinished(@Status int status, @Nullable Uri uri, Exception exception) {
|
||||||
getContext().getContentResolver().notifyChange(toNotifyUri(uri), null, false);
|
getContext().getContentResolver().notifyChange(toNotifyUri(uri), null, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final OnTaskFinishedCallback<DocumentMetadata> mLoadChildrenCallback =
|
private final OnTaskFinishedCallback<DocumentMetadata> mLoadChildrenCallback =
|
||||||
new OnTaskFinishedCallback<DocumentMetadata>() {
|
new OnTaskFinishedCallback<DocumentMetadata>() {
|
||||||
@Override
|
@Override
|
||||||
public void onTaskFinished(@Status int status, DocumentMetadata metadata,
|
public void onTaskFinished(@Status int status, DocumentMetadata metadata,
|
||||||
Exception exception) {
|
Exception exception) {
|
||||||
// Notify remote side that we get the list even though we don't have the stat yet.
|
// 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.
|
// If it failed we still should notify the remote side that the loading failed.
|
||||||
getContext().getContentResolver().notifyChange(
|
getContext().getContentResolver().notifyChange(
|
||||||
toNotifyUri(metadata.getUri()), null, false);
|
toNotifyUri(metadata.getUri()), null, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final OnTaskFinishedCallback<String> mWriteFinishedCallback =
|
private final OnTaskFinishedCallback<String> mWriteFinishedCallback =
|
||||||
new OnTaskFinishedCallback<String>() {
|
new OnTaskFinishedCallback<String>() {
|
||||||
@Override
|
@Override
|
||||||
public void onTaskFinished(
|
public void onTaskFinished(
|
||||||
@Status int status, @Nullable String item, Exception exception) {
|
@Status int status, @Nullable String item, Exception exception) {
|
||||||
final Uri uri = toUri(item);
|
final Uri uri = toUri(item);
|
||||||
try (final CacheResult result = mCache.get(uri)) {
|
try (final CacheResult result = mCache.get(uri)) {
|
||||||
if (result.getState() != CacheResult.CACHE_MISS) {
|
if (result.getState() != CacheResult.CACHE_MISS) {
|
||||||
result.getItem().reset();
|
result.getItem().reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final Uri parentUri = DocumentMetadata.buildParentUri(uri);
|
final Uri parentUri = DocumentMetadata.buildParentUri(uri);
|
||||||
getContext().getContentResolver().notifyChange(toNotifyUri(parentUri), null, false);
|
getContext().getContentResolver().notifyChange(toNotifyUri(parentUri), null, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final OnTaskFinishedCallback<List<String>> mLoadSharesFinishedCallback =
|
private final OnTaskFinishedCallback<List<String>> mLoadSharesFinishedCallback =
|
||||||
new OnTaskFinishedCallback<List<String>>() {
|
new OnTaskFinishedCallback<List<String>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onTaskFinished(
|
public void onTaskFinished(
|
||||||
@OnTaskFinishedCallback.Status int status,
|
@OnTaskFinishedCallback.Status int status,
|
||||||
@Nullable List<String> item,
|
@Nullable List<String> item,
|
||||||
@Nullable Exception exception) {
|
@Nullable Exception exception) {
|
||||||
if (BuildConfig.DEBUG) Log.d(TAG, "Browsing callback");
|
if (BuildConfig.DEBUG) Log.d(TAG, "Browsing callback");
|
||||||
|
|
||||||
mBrowsingStorage = item;
|
mBrowsingStorage = item;
|
||||||
|
|
||||||
getContext().getContentResolver().notifyChange(
|
getContext().getContentResolver().notifyChange(
|
||||||
toNotifyUri(toUri(NetworkBrowser.SMB_BROWSING_URI.toString())), null, false);
|
toNotifyUri(toUri(NetworkBrowser.SMB_BROWSING_URI.toString())), null, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final MountedShareChangeListener mShareChangeListener = new MountedShareChangeListener() {
|
private final MountedShareChangeListener mShareChangeListener = new MountedShareChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -162,7 +162,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
private StorageManager mStorageManager;
|
private StorageManager mStorageManager;
|
||||||
private NetworkBrowser mNetworkBrowser;
|
private NetworkBrowser mNetworkBrowser;
|
||||||
|
|
||||||
private List<String> mBrowsingStorage = new ArrayList<>();
|
private List<String> mBrowsingStorage;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreate() {
|
public boolean onCreate() {
|
||||||
|
@ -189,11 +189,11 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
MatrixCursor cursor = new MatrixCursor(projection, mShareManager.size());
|
MatrixCursor cursor = new MatrixCursor(projection, mShareManager.size());
|
||||||
|
|
||||||
cursor.addRow(new Object[] {
|
cursor.addRow(new Object[] {
|
||||||
NetworkBrowser.SMB_BROWSING_URI.toString(),
|
NetworkBrowser.SMB_BROWSING_URI.toString(),
|
||||||
NetworkBrowser.SMB_BROWSING_URI.toString(),
|
NetworkBrowser.SMB_BROWSING_URI.toString(),
|
||||||
getContext().getResources().getString(R.string.browsing_root_name),
|
getContext().getResources().getString(R.string.browsing_root_name),
|
||||||
0,
|
0,
|
||||||
R.drawable.ic_cloud,
|
R.drawable.ic_cloud,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (String uri : mShareManager) {
|
for (String uri : mShareManager) {
|
||||||
|
@ -211,11 +211,11 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
name = metadata.getDisplayName();
|
name = metadata.getDisplayName();
|
||||||
|
|
||||||
cursor.addRow(new Object[] {
|
cursor.addRow(new Object[] {
|
||||||
toRootId(metadata),
|
toRootId(metadata),
|
||||||
toDocumentId(parsedUri),
|
toDocumentId(parsedUri),
|
||||||
name,
|
name,
|
||||||
Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD,
|
Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD,
|
||||||
R.drawable.ic_folder_shared
|
R.drawable.ic_folder_shared
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,7 +273,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder)
|
public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder)
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
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;
|
||||||
|
|
||||||
|
@ -304,7 +304,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
mCache.throwLastExceptionIfAny(uri);
|
mCache.throwLastExceptionIfAny(uri);
|
||||||
|
|
||||||
final LoadDocumentTask task =
|
final LoadDocumentTask task =
|
||||||
new LoadDocumentTask(uri, mClient, mCache, mLoadDocumentCallback);
|
new LoadDocumentTask(uri, mClient, mCache, mLoadDocumentCallback);
|
||||||
mTaskManager.runTask(uri, task);
|
mTaskManager.runTask(uri, task);
|
||||||
cursor.setLoadingTask(task);
|
cursor.setLoadingTask(task);
|
||||||
|
|
||||||
|
@ -321,7 +321,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
final Map<Uri, DocumentMetadata> childrenMap = metadata.getChildren();
|
final Map<Uri, DocumentMetadata> childrenMap = metadata.getChildren();
|
||||||
if (childrenMap == null || result.getState() == CacheResult.CACHE_EXPIRED) {
|
if (childrenMap == null || result.getState() == CacheResult.CACHE_EXPIRED) {
|
||||||
final LoadChildrenTask task =
|
final LoadChildrenTask task =
|
||||||
new LoadChildrenTask(metadata, mClient, mCache, mLoadChildrenCallback);
|
new LoadChildrenTask(metadata, mClient, mCache, mLoadChildrenCallback);
|
||||||
mTaskManager.runTask(uri, task);
|
mTaskManager.runTask(uri, task);
|
||||||
cursor.setLoadingTask(task);
|
cursor.setLoadingTask(task);
|
||||||
|
|
||||||
|
@ -340,13 +340,13 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
}
|
}
|
||||||
if (!isLoading && !docMap.isEmpty()) {
|
if (!isLoading && !docMap.isEmpty()) {
|
||||||
LoadStatTask task = new LoadStatTask(docMap, mClient,
|
LoadStatTask task = new LoadStatTask(docMap, mClient,
|
||||||
new OnTaskFinishedCallback<Map<Uri, DocumentMetadata>>() {
|
new OnTaskFinishedCallback<Map<Uri, DocumentMetadata>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onTaskFinished(
|
public void onTaskFinished(
|
||||||
@Status int status, Map<Uri, DocumentMetadata> item, Exception exception) {
|
@Status int status, Map<Uri, DocumentMetadata> item, Exception exception) {
|
||||||
getContext().getContentResolver().notifyChange(notifyUri, null, false);
|
getContext().getContentResolver().notifyChange(notifyUri, null, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mTaskManager.runTask(uri, task);
|
mTaskManager.runTask(uri, task);
|
||||||
cursor.setLoadingTask(task);
|
cursor.setLoadingTask(task);
|
||||||
|
|
||||||
|
@ -381,7 +381,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object[] getDocumentValues(
|
private Object[] getDocumentValues(
|
||||||
String[] projection, DocumentMetadata metadata) {
|
String[] projection, DocumentMetadata metadata) {
|
||||||
Object[] row = new Object[projection.length];
|
Object[] row = new Object[projection.length];
|
||||||
for (int i = 0; i < projection.length; ++i) {
|
for (int i = 0; i < projection.length; ++i) {
|
||||||
switch (projection[i]) {
|
switch (projection[i]) {
|
||||||
|
@ -461,7 +461,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
|
|
||||||
final Uri uri = toUri(NetworkBrowser.SMB_BROWSING_URI.toString());
|
final Uri uri = toUri(NetworkBrowser.SMB_BROWSING_URI.toString());
|
||||||
|
|
||||||
if (mBrowsingStorage.isEmpty()) {
|
if (mBrowsingStorage == null) {
|
||||||
AsyncTask serversTask = mNetworkBrowser.getServersAsync(mLoadSharesFinishedCallback);
|
AsyncTask serversTask = mNetworkBrowser.getServersAsync(mLoadSharesFinishedCallback);
|
||||||
|
|
||||||
Bundle extra = new Bundle();
|
Bundle extra = new Bundle();
|
||||||
|
@ -475,7 +475,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
cursor.addRow(getCursorRowForServer(projection, server));
|
cursor.addRow(getCursorRowForServer(projection, server));
|
||||||
}
|
}
|
||||||
|
|
||||||
mBrowsingStorage.clear();
|
mBrowsingStorage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cursor;
|
return cursor;
|
||||||
|
@ -483,15 +483,15 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String createDocument(String parentDocumentId, String mimeType, String displayName)
|
public String createDocument(String parentDocumentId, String mimeType, String displayName)
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
try {
|
try {
|
||||||
final Uri parentUri = toUri(parentDocumentId);
|
final Uri parentUri = toUri(parentDocumentId);
|
||||||
|
|
||||||
boolean isDir = Document.MIME_TYPE_DIR.equals(mimeType);
|
boolean isDir = Document.MIME_TYPE_DIR.equals(mimeType);
|
||||||
final DirectoryEntry entry = new DirectoryEntry(
|
final DirectoryEntry entry = new DirectoryEntry(
|
||||||
isDir ? DirectoryEntry.DIR : DirectoryEntry.FILE,
|
isDir ? DirectoryEntry.DIR : DirectoryEntry.FILE,
|
||||||
"", // comment
|
"", // comment
|
||||||
displayName);
|
displayName);
|
||||||
final Uri uri = DocumentMetadata.buildChildUri(parentUri, entry);
|
final Uri uri = DocumentMetadata.buildChildUri(parentUri, entry);
|
||||||
|
|
||||||
if (isDir) {
|
if (isDir) {
|
||||||
|
@ -617,15 +617,15 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeDocument(String documentId, String parentDocumentId)
|
public void removeDocument(String documentId, String parentDocumentId)
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
// documentId is hierarchical. It can only have one parent.
|
// documentId is hierarchical. It can only have one parent.
|
||||||
deleteDocument(documentId);
|
deleteDocument(documentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String moveDocument(
|
public String moveDocument(
|
||||||
String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId)
|
String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId)
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
try {
|
try {
|
||||||
final Uri uri = toUri(sourceDocumentId);
|
final Uri uri = toUri(sourceDocumentId);
|
||||||
final Uri targetParentUri = toUri(targetParentDocumentId);
|
final Uri targetParentUri = toUri(targetParentDocumentId);
|
||||||
|
@ -637,19 +637,19 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
final List<String> pathSegmentsOfSource = uri.getPathSegments();
|
final List<String> pathSegmentsOfSource = uri.getPathSegments();
|
||||||
final List<String> pathSegmentsOfTargetParent = targetParentUri.getPathSegments();
|
final List<String> pathSegmentsOfTargetParent = targetParentUri.getPathSegments();
|
||||||
if (pathSegmentsOfSource.isEmpty() ||
|
if (pathSegmentsOfSource.isEmpty() ||
|
||||||
pathSegmentsOfTargetParent.isEmpty() ||
|
pathSegmentsOfTargetParent.isEmpty() ||
|
||||||
!Objects.equals(pathSegmentsOfSource.get(0), pathSegmentsOfTargetParent.get(0))) {
|
!Objects.equals(pathSegmentsOfSource.get(0), pathSegmentsOfTargetParent.get(0))) {
|
||||||
throw new UnsupportedOperationException("Instance move across shares are not supported.");
|
throw new UnsupportedOperationException("Instance move across shares are not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
final Uri targetUri = DocumentMetadata
|
final Uri targetUri = DocumentMetadata
|
||||||
.buildChildUri(targetParentUri, uri.getLastPathSegment());
|
.buildChildUri(targetParentUri, uri.getLastPathSegment());
|
||||||
mClient.rename(uri.toString(), targetUri.toString());
|
mClient.rename(uri.toString(), targetUri.toString());
|
||||||
|
|
||||||
revokeDocumentPermission(sourceDocumentId);
|
revokeDocumentPermission(sourceDocumentId);
|
||||||
|
|
||||||
getContext().getContentResolver()
|
getContext().getContentResolver()
|
||||||
.notifyChange(toNotifyUri(DocumentMetadata.buildParentUri(uri)), null, false);
|
.notifyChange(toNotifyUri(DocumentMetadata.buildParentUri(uri)), null, false);
|
||||||
getContext().getContentResolver().notifyChange(toNotifyUri(targetParentUri), null, false);
|
getContext().getContentResolver().notifyChange(toNotifyUri(targetParentUri), null, false);
|
||||||
|
|
||||||
try (CacheResult result = mCache.get(uri)) {
|
try (CacheResult result = mCache.get(uri)) {
|
||||||
|
@ -672,7 +672,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParcelFileDescriptor openDocument(String documentId, String mode,
|
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);
|
if (BuildConfig.DEBUG) Log.d(TAG, "Opening document " + documentId + " with mode " + mode);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -680,7 +680,7 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
OnTaskFinishedCallback<String> callback =
|
OnTaskFinishedCallback<String> callback =
|
||||||
mode.contains("w") ? mWriteFinishedCallback : null;
|
mode.contains("w") ? mWriteFinishedCallback : null;
|
||||||
return mClient.openProxyFile(
|
return mClient.openProxyFile(
|
||||||
uri,
|
uri,
|
||||||
mode,
|
mode,
|
||||||
|
@ -709,13 +709,13 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "r": {
|
case "r": {
|
||||||
final ReadFileTask task = new ReadFileTask(
|
final ReadFileTask task = new ReadFileTask(
|
||||||
uri, mClient, pipe[1], mBufferPool);
|
uri, mClient, pipe[1], mBufferPool);
|
||||||
mTaskManager.runIoTask(task);
|
mTaskManager.runIoTask(task);
|
||||||
}
|
}
|
||||||
return pipe[0];
|
return pipe[0];
|
||||||
case "w": {
|
case "w": {
|
||||||
final WriteFileTask task =
|
final WriteFileTask task =
|
||||||
new WriteFileTask(uri, mClient, pipe[0], mBufferPool, mWriteFinishedCallback);
|
new WriteFileTask(uri, mClient, pipe[0], mBufferPool, mWriteFinishedCallback);
|
||||||
mTaskManager.runIoTask(task);
|
mTaskManager.runIoTask(task);
|
||||||
return pipe[1];
|
return pipe[1];
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue