From 65f6f6799586f05b892dedc77dae70883fcef742 Mon Sep 17 00:00:00 2001 From: rthakohov Date: Thu, 27 Jul 2017 15:45:06 -0700 Subject: [PATCH] Support network browsing via NetBIOS broadcast If the master browsing approach fails, use NetBIOS broadcast to discover Samba servers as a fallback. --- .../browsing/BrowsingException.java | 8 +- .../browsing/NetworkBrowser.java | 30 +-- .../browsing/NetworkBrowsingProvider.java | 2 +- .../broadcast/BroadcastBrowsingProvider.java | 129 ++++++++++++ .../browsing/broadcast/BroadcastUtils.java | 183 +++++++++++++++++ .../provider/SambaDocumentsProvider.java | 186 +++++++++--------- 6 files changed, 430 insertions(+), 108 deletions(-) create mode 100644 app/src/main/java/com/google/android/sambadocumentsprovider/browsing/broadcast/BroadcastBrowsingProvider.java create mode 100644 app/src/main/java/com/google/android/sambadocumentsprovider/browsing/broadcast/BroadcastUtils.java diff --git a/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/BrowsingException.java b/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/BrowsingException.java index 8fb304b..561633d 100644 --- a/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/BrowsingException.java +++ b/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/BrowsingException.java @@ -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); + } } diff --git a/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/NetworkBrowser.java b/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/NetworkBrowser.java index 0083eeb..15497ce 100644 --- a/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/NetworkBrowser.java +++ b/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/NetworkBrowser.java @@ -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 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 getServers() throws BrowsingException { - return mMasterProvider.getServers(); + List 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> { diff --git a/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/NetworkBrowsingProvider.java b/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/NetworkBrowsingProvider.java index 9aee90a..1eeaefa 100644 --- a/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/NetworkBrowsingProvider.java +++ b/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/NetworkBrowsingProvider.java @@ -19,7 +19,7 @@ package com.google.android.sambadocumentsprovider.browsing; import java.util.List; -interface NetworkBrowsingProvider { +public interface NetworkBrowsingProvider { /** * Returns unresolved host names. */ diff --git a/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/broadcast/BroadcastBrowsingProvider.java b/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/broadcast/BroadcastBrowsingProvider.java new file mode 100644 index 0000000..96b22d6 --- /dev/null +++ b/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/broadcast/BroadcastBrowsingProvider.java @@ -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 . + */ + +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 getServers() throws BrowsingException { + try { + List>> serverFutures = new ArrayList<>(); + + List broadcastAddresses = BroadcastUtils.getBroadcastAddress(); + for (String address : broadcastAddresses) { + mTransId++; + serverFutures.add(mExecutor.submit(new GetServersForInterfaceTask(address, mTransId))); + } + + List servers = new ArrayList<>(); + for (Future> 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> { + private final String mBroadcastAddress; + private final int mTransId; + + GetServersForInterfaceTask(String broadcastAddress, int transId) { + mBroadcastAddress = broadcastAddress; + mTransId = transId; + } + + @Override + public List 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 listenForServers(DatagramSocket socket) throws IOException { + List 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; + } + } +} diff --git a/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/broadcast/BroadcastUtils.java b/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/broadcast/BroadcastUtils.java new file mode 100644 index 0000000..d802229 --- /dev/null +++ b/app/src/main/java/com/google/android/sambadocumentsprovider/browsing/broadcast/BroadcastUtils.java @@ -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 . + */ + +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 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 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 getBroadcastAddress() throws BrowsingException, SocketException { + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + + List 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(); + } + } +} diff --git a/app/src/main/java/com/google/android/sambadocumentsprovider/provider/SambaDocumentsProvider.java b/app/src/main/java/com/google/android/sambadocumentsprovider/provider/SambaDocumentsProvider.java index 787b143..3089946 100644 --- a/app/src/main/java/com/google/android/sambadocumentsprovider/provider/SambaDocumentsProvider.java +++ b/app/src/main/java/com/google/android/sambadocumentsprovider/provider/SambaDocumentsProvider.java @@ -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 mLoadDocumentCallback = - new OnTaskFinishedCallback() { - @Override - public void onTaskFinished(@Status int status, @Nullable Uri uri, Exception exception) { - getContext().getContentResolver().notifyChange(toNotifyUri(uri), null, false); - } - }; + new OnTaskFinishedCallback() { + @Override + public void onTaskFinished(@Status int status, @Nullable Uri uri, Exception exception) { + getContext().getContentResolver().notifyChange(toNotifyUri(uri), null, false); + } + }; private final OnTaskFinishedCallback mLoadChildrenCallback = - new OnTaskFinishedCallback() { - @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() { + @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 mWriteFinishedCallback = - new OnTaskFinishedCallback() { - @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() { + @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> mLoadSharesFinishedCallback = - new OnTaskFinishedCallback>() { - @Override - public void onTaskFinished( - @OnTaskFinishedCallback.Status int status, - @Nullable List item, - @Nullable Exception exception) { - if (BuildConfig.DEBUG) Log.d(TAG, "Browsing callback"); + new OnTaskFinishedCallback>() { + @Override + public void onTaskFinished( + @OnTaskFinishedCallback.Status int status, + @Nullable List 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 mBrowsingStorage = new ArrayList<>(); + private List 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 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>() { - @Override - public void onTaskFinished( - @Status int status, Map item, Exception exception) { - getContext().getContentResolver().notifyChange(notifyUri, null, false); - } - }); + new OnTaskFinishedCallback>() { + @Override + public void onTaskFinished( + @Status int status, Map 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 pathSegmentsOfSource = uri.getPathSegments(); final List 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 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]; }