Move browsing functionality to the launch activity
The input for share URI has a drop-down list displaying discovered shares.
This commit is contained in:
parent
751d538af4
commit
12eb8f2fad
12 changed files with 622 additions and 125 deletions
|
@ -22,35 +22,85 @@ 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.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;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class discovers Samba servers and shares under them available on the local network.
|
||||||
|
*/
|
||||||
public class NetworkBrowser {
|
public class NetworkBrowser {
|
||||||
public static final Uri SMB_BROWSING_URI = Uri.parse("smb://");
|
private static final Uri SMB_BROWSING_URI = Uri.parse("smb://");
|
||||||
|
|
||||||
private static final String TAG = "NetworkBrowser";
|
private static final String TAG = "NetworkBrowser";
|
||||||
|
|
||||||
private final NetworkBrowsingProvider mMasterProvider;
|
private final NetworkBrowsingProvider mMasterProvider;
|
||||||
private final NetworkBrowsingProvider mBroadcastProvider;
|
private final NetworkBrowsingProvider mBroadcastProvider;
|
||||||
private final TaskManager mTaskManager;
|
private final TaskManager mTaskManager;
|
||||||
|
private final SmbClient mClient;
|
||||||
|
|
||||||
public NetworkBrowser(SmbClient client, TaskManager taskManager) {
|
public NetworkBrowser(SmbClient client, TaskManager taskManager) {
|
||||||
mMasterProvider = new MasterBrowsingProvider(client);
|
mMasterProvider = new MasterBrowsingProvider(client);
|
||||||
mBroadcastProvider = new BroadcastBrowsingProvider();
|
mBroadcastProvider = new BroadcastBrowsingProvider();
|
||||||
mTaskManager = taskManager;
|
mTaskManager = taskManager;
|
||||||
|
mClient = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AsyncTask getServersAsync(OnTaskFinishedCallback<List<String>> callback) {
|
/**
|
||||||
AsyncTask<Void, Void, List<String>> loadServersTask = new LoadServersTask(callback);
|
* Asynchronously get available servers and shares under them.
|
||||||
|
* A server name is mapped to the list of its children.
|
||||||
|
*/
|
||||||
|
public AsyncTask getSharesAsync(OnTaskFinishedCallback<Map<String, List<String>>> callback) {
|
||||||
|
AsyncTask<Void, Void, Map<String, List<String>>> loadServersTask = new LoadServersTask(callback);
|
||||||
|
|
||||||
mTaskManager.runTask(SMB_BROWSING_URI, loadServersTask);
|
mTaskManager.runTask(SMB_BROWSING_URI, loadServersTask);
|
||||||
|
|
||||||
return loadServersTask;
|
return loadServersTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, List<String>> getShares() throws BrowsingException {
|
||||||
|
List<String> servers = getServers();
|
||||||
|
|
||||||
|
Map<String, List<String>> shares = new HashMap<>();
|
||||||
|
|
||||||
|
for (String server : servers) {
|
||||||
|
try {
|
||||||
|
shares.put(server, getSharesForServer(server));
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to load shares for server", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return shares;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getSharesForServer(String server) throws IOException {
|
||||||
|
List<String> shares = new ArrayList<>();
|
||||||
|
|
||||||
|
String serverUri = SMB_BROWSING_URI + server;
|
||||||
|
SmbDir serverDir = mClient.openDir(serverUri);
|
||||||
|
|
||||||
|
DirectoryEntry shareEntry;
|
||||||
|
while ((shareEntry = serverDir.readDir()) != null) {
|
||||||
|
if (shareEntry.getType() == DirectoryEntry.FILE_SHARE) {
|
||||||
|
shares.add(serverUri + "/" + shareEntry.getName().trim());
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Unsupported entry type: " + shareEntry.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return shares;
|
||||||
|
}
|
||||||
|
|
||||||
private List<String> getServers() throws BrowsingException {
|
private List<String> getServers() throws BrowsingException {
|
||||||
List<String> servers = null;
|
List<String> servers = null;
|
||||||
|
|
||||||
|
@ -67,23 +117,19 @@ public class NetworkBrowser {
|
||||||
return servers;
|
return servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LoadServersTask extends AsyncTask<Void, Void, List<String>> {
|
private class LoadServersTask extends AsyncTask<Void, Void, Map<String, List<String>>> {
|
||||||
final OnTaskFinishedCallback<List<String>> mCallback;
|
final OnTaskFinishedCallback<Map<String, List<String>>> mCallback;
|
||||||
|
|
||||||
private BrowsingException mException;
|
private BrowsingException mException;
|
||||||
|
|
||||||
LoadServersTask(OnTaskFinishedCallback<List<String>> callback) {
|
LoadServersTask(OnTaskFinishedCallback<Map<String, List<String>>> callback) {
|
||||||
mCallback = callback;
|
mCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> loadData() throws BrowsingException {
|
|
||||||
return getServers();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<String> doInBackground(Void... voids) {
|
protected Map<String, List<String>> doInBackground(Void... voids) {
|
||||||
try {
|
try {
|
||||||
return loadData();
|
return getShares();
|
||||||
} catch (BrowsingException e) {
|
} catch (BrowsingException e) {
|
||||||
Log.e(TAG, "Failed to load data for network browsing: ", e);
|
Log.e(TAG, "Failed to load data for network browsing: ", e);
|
||||||
mException = e;
|
mException = e;
|
||||||
|
@ -91,7 +137,7 @@ public class NetworkBrowser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onPostExecute(List<String> servers) {
|
protected void onPostExecute(Map<String, List<String>> servers) {
|
||||||
if (servers != null) {
|
if (servers != null) {
|
||||||
mCallback.onTaskFinished(OnTaskFinishedCallback.SUCCEEDED, servers, null);
|
mCallback.onTaskFinished(OnTaskFinishedCallback.SUCCEEDED, servers, null);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -30,7 +30,9 @@ import java.net.InetAddress;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
@ -57,12 +59,12 @@ public class BroadcastBrowsingProvider implements NetworkBrowsingProvider {
|
||||||
serverFutures.add(mExecutor.submit(new GetServersForInterfaceTask(address, mTransId)));
|
serverFutures.add(mExecutor.submit(new GetServersForInterfaceTask(address, mTransId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> servers = new ArrayList<>();
|
Set<String> servers = new HashSet<>();
|
||||||
for (Future<List<String>> future : serverFutures) {
|
for (Future<List<String>> future : serverFutures) {
|
||||||
servers.addAll(future.get());
|
servers.addAll(future.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
return servers;
|
return new ArrayList<>(servers);
|
||||||
} catch (IOException | ExecutionException | InterruptedException e) {
|
} catch (IOException | ExecutionException | InterruptedException e) {
|
||||||
Log.e(TAG, "Failed to get servers via broadcast", e);
|
Log.e(TAG, "Failed to get servers via broadcast", e);
|
||||||
throw new BrowsingException("Failed to get servers via broadcast", e);
|
throw new BrowsingException("Failed to get servers via broadcast", e);
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 Google Inc.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.sambadocumentsprovider.mount;
|
||||||
|
|
||||||
|
import android.widget.Filter;
|
||||||
|
import android.widget.Filterable;
|
||||||
|
|
||||||
|
import com.google.android.sambadocumentsprovider.R;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BrowsingAutocompleteAdapter extends SectionedAdapter implements Filterable {
|
||||||
|
private final List<ServerInfo> mServers = new ArrayList<>();
|
||||||
|
|
||||||
|
private List<ServerInfo> mFilteredServers = new ArrayList<>();
|
||||||
|
|
||||||
|
public void addServer(String name, List<String> children) {
|
||||||
|
synchronized (mServers) {
|
||||||
|
mServers.add(new ServerInfo(name, children));
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Filter getFilter() {
|
||||||
|
return new Filter() {
|
||||||
|
@Override
|
||||||
|
protected FilterResults performFiltering(CharSequence charSequence) {
|
||||||
|
String query = charSequence == null ? "" : charSequence.toString().toLowerCase();
|
||||||
|
|
||||||
|
List<ServerInfo> filteredServers = new ArrayList<>();
|
||||||
|
|
||||||
|
if (!query.isEmpty()) {
|
||||||
|
filteredServers.add(new ServerInfo(query, Collections.<String>emptyList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLoadingData()) {
|
||||||
|
synchronized (mServers) {
|
||||||
|
for (ServerInfo serverInfo : mServers) {
|
||||||
|
|
||||||
|
List<String> displayedShares = new ArrayList<>();
|
||||||
|
for (String share : serverInfo.getChildren()) {
|
||||||
|
if (share.toLowerCase().contains(query)) {
|
||||||
|
displayedShares.add(share);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!displayedShares.isEmpty()) {
|
||||||
|
filteredServers.add(new ServerInfo(serverInfo.getName(), displayedShares));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterResults results = new FilterResults();
|
||||||
|
results.values = filteredServers;
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
|
||||||
|
mFilteredServers = (List<ServerInfo>) filterResults.values;
|
||||||
|
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSectionCount() {
|
||||||
|
return mFilteredServers.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNameForSection(int sectionIndex) {
|
||||||
|
return mFilteredServers.get(sectionIndex).getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIconForSection(int sectionIndex) {
|
||||||
|
return R.drawable.ic_server;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSizeForSection(int sectionIndex) {
|
||||||
|
return mFilteredServers.get(sectionIndex).getChildren().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNameForRow(int sectionIndex, int rowIndex) {
|
||||||
|
return mFilteredServers.get(sectionIndex).getChildren().get(rowIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIconForRow(int sectionIndex, int rowIndex) {
|
||||||
|
return R.drawable.ic_folder_shared;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ServerInfo {
|
||||||
|
private final String mName;
|
||||||
|
private final List<String> mChildren;
|
||||||
|
|
||||||
|
ServerInfo(String name, List<String> children) {
|
||||||
|
this.mName = name;
|
||||||
|
this.mChildren = children;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getName() {
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> getChildren() {
|
||||||
|
return mChildren;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 Google Inc.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.sambadocumentsprovider.mount;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
public class BrowsingAutocompleteTextView extends android.support.v7.widget.AppCompatAutoCompleteTextView {
|
||||||
|
public BrowsingAutocompleteTextView(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BrowsingAutocompleteTextView(Context context, AttributeSet attributeSet) {
|
||||||
|
super(context, attributeSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BrowsingAutocompleteTextView(Context context, AttributeSet attributeSet, int v) {
|
||||||
|
super(context, attributeSet, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean enoughToFilter() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void filter() {
|
||||||
|
int length = getText().length();
|
||||||
|
performFiltering(getText(), length == 0 ? 0 : getText().charAt(length - 1));
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.view.View.OnKeyListener;
|
import android.view.View.OnKeyListener;
|
||||||
|
@ -52,19 +53,20 @@ import com.google.android.sambadocumentsprovider.ShareManager;
|
||||||
import com.google.android.sambadocumentsprovider.TaskManager;
|
import com.google.android.sambadocumentsprovider.TaskManager;
|
||||||
import com.google.android.sambadocumentsprovider.base.AuthFailedException;
|
import com.google.android.sambadocumentsprovider.base.AuthFailedException;
|
||||||
import com.google.android.sambadocumentsprovider.base.OnTaskFinishedCallback;
|
import com.google.android.sambadocumentsprovider.base.OnTaskFinishedCallback;
|
||||||
|
import com.google.android.sambadocumentsprovider.browsing.NetworkBrowser;
|
||||||
import com.google.android.sambadocumentsprovider.cache.DocumentCache;
|
import com.google.android.sambadocumentsprovider.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;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class MountServerActivity extends AppCompatActivity {
|
public class MountServerActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private static final String TAG = "MountServerActivity";
|
private static final String TAG = "MountServerActivity";
|
||||||
|
|
||||||
private static final String ACTION_BROWSE = "android.provider.action.BROWSE";
|
private static final String ACTION_BROWSE = "android.provider.action.BROWSE";
|
||||||
|
|
||||||
private static final String SHARE_PATH_KEY = "sharePath";
|
private static final String SHARE_PATH_KEY = "sharePath";
|
||||||
private static final String NEEDS_PASSWORD_KEY = "needsPassword";
|
private static final String NEEDS_PASSWORD_KEY = "needsPassword";
|
||||||
private static final String DOMAIN_KEY = "domain";
|
private static final String DOMAIN_KEY = "domain";
|
||||||
|
@ -100,15 +102,34 @@ public class MountServerActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private final OnTaskFinishedCallback<Map<String, List<String>>> mBrowsingCallback
|
||||||
|
= new OnTaskFinishedCallback<Map<String, List<String>>>() {
|
||||||
|
@Override
|
||||||
|
public void onTaskFinished(
|
||||||
|
@Status int status, @Nullable Map<String, List<String>> result, @Nullable Exception exception) {
|
||||||
|
|
||||||
|
for (String server : result.keySet()) {
|
||||||
|
mBrowsingAdapter.addServer(server, result.get(server));
|
||||||
|
}
|
||||||
|
|
||||||
|
mBrowsingAdapter.finishLoading();
|
||||||
|
|
||||||
|
if (mSharePathEditText.isPopupShowing()) {
|
||||||
|
mSharePathEditText.filter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private DocumentCache mCache;
|
private DocumentCache mCache;
|
||||||
private TaskManager mTaskManager;
|
private TaskManager mTaskManager;
|
||||||
private ShareManager mShareManager;
|
private ShareManager mShareManager;
|
||||||
private SmbClient mClient;
|
private SmbClient mClient;
|
||||||
|
private BrowsingAutocompleteAdapter mBrowsingAdapter;
|
||||||
|
|
||||||
private CheckBox mNeedPasswordCheckbox;
|
private CheckBox mNeedPasswordCheckbox;
|
||||||
private View mPasswordHideGroup;
|
private View mPasswordHideGroup;
|
||||||
|
|
||||||
private EditText mSharePathEditText;
|
private BrowsingAutocompleteTextView mSharePathEditText;
|
||||||
private EditText mDomainEditText;
|
private EditText mDomainEditText;
|
||||||
private EditText mUsernameEditText;
|
private EditText mUsernameEditText;
|
||||||
private EditText mPasswordEditText;
|
private EditText mPasswordEditText;
|
||||||
|
@ -130,7 +151,7 @@ public class MountServerActivity extends AppCompatActivity {
|
||||||
|
|
||||||
mPasswordHideGroup = findViewById(R.id.password_hide_group);
|
mPasswordHideGroup = findViewById(R.id.password_hide_group);
|
||||||
|
|
||||||
mSharePathEditText = (EditText) findViewById(R.id.share_path);
|
mSharePathEditText = (BrowsingAutocompleteTextView) findViewById(R.id.share_path);
|
||||||
mSharePathEditText.setOnKeyListener(mMountKeyListener);
|
mSharePathEditText.setOnKeyListener(mMountKeyListener);
|
||||||
|
|
||||||
mUsernameEditText = (EditText) findViewById(R.id.username);
|
mUsernameEditText = (EditText) findViewById(R.id.username);
|
||||||
|
@ -158,6 +179,8 @@ public class MountServerActivity extends AppCompatActivity {
|
||||||
mConnectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
|
mConnectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
|
||||||
|
|
||||||
restoreSavedInstanceState(savedInstanceState);
|
restoreSavedInstanceState(savedInstanceState);
|
||||||
|
|
||||||
|
startBrowsing();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restoreSavedInstanceState(@Nullable Bundle savedInstanceState) {
|
private void restoreSavedInstanceState(@Nullable Bundle savedInstanceState) {
|
||||||
|
@ -220,6 +243,23 @@ public class MountServerActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startBrowsing() {
|
||||||
|
mSharePathEditText.setOnTouchListener(new View.OnTouchListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View view, MotionEvent motionEvent) {
|
||||||
|
mSharePathEditText.filter();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mBrowsingAdapter = new BrowsingAutocompleteAdapter();
|
||||||
|
mSharePathEditText.setAdapter(mBrowsingAdapter);
|
||||||
|
mSharePathEditText.setThreshold(0);
|
||||||
|
|
||||||
|
NetworkBrowser browser = new NetworkBrowser(mClient, mTaskManager);
|
||||||
|
browser.getSharesAsync(mBrowsingCallback);
|
||||||
|
}
|
||||||
|
|
||||||
private void tryMount() {
|
private void tryMount() {
|
||||||
final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
|
final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
|
||||||
if (info == null || !info.isConnected()) {
|
if (info == null || !info.isConnected()) {
|
||||||
|
|
|
@ -0,0 +1,246 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 Google Inc.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.sambadocumentsprovider.mount;
|
||||||
|
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.google.android.sambadocumentsprovider.R;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class SectionedAdapter extends BaseAdapter {
|
||||||
|
private static final String TAG = "SectionedAdapter";
|
||||||
|
|
||||||
|
@IntDef({SECTION_HEADER, REGULAR_ROW, LOADING_ROW})
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
private @interface Type {}
|
||||||
|
private static final int SECTION_HEADER = 0;
|
||||||
|
private static final int REGULAR_ROW = 1;
|
||||||
|
private static final int LOADING_ROW = 2;
|
||||||
|
|
||||||
|
private List<FlatRow> mFlatRows = new ArrayList<>();
|
||||||
|
|
||||||
|
private volatile boolean mIsLoadingData = true;
|
||||||
|
|
||||||
|
public void finishLoading() {
|
||||||
|
mIsLoadingData = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract int getSectionCount();
|
||||||
|
|
||||||
|
public abstract String getNameForSection(int sectionIndex);
|
||||||
|
public abstract int getIconForSection(int sectionIndex);
|
||||||
|
public abstract int getSizeForSection(int sectionIndex);
|
||||||
|
|
||||||
|
public abstract String getNameForRow(int sectionIndex, int rowIndex);
|
||||||
|
public abstract int getIconForRow(int sectionIndex, int rowIndex);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return mFlatRows.size() + (mIsLoadingData ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FlatRow getItem(int position) {
|
||||||
|
if (position == mFlatRows.size() && mIsLoadingData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFlatRows.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Type int getItemViewType(int position) {
|
||||||
|
if (position == mFlatRows.size()) {
|
||||||
|
return LOADING_ROW;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFlatRows.get(position).isSectionHeader() ? SECTION_HEADER : REGULAR_ROW;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View reuseView, ViewGroup parent) {
|
||||||
|
int viewType = getItemViewType(position);
|
||||||
|
|
||||||
|
if (reuseView == null) {
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||||
|
|
||||||
|
switch (viewType) {
|
||||||
|
case LOADING_ROW:
|
||||||
|
reuseView = inflater.inflate(R.layout.browsing_progress_item_layout, null);
|
||||||
|
break;
|
||||||
|
case SECTION_HEADER:
|
||||||
|
case REGULAR_ROW:
|
||||||
|
reuseView = inflater.inflate(R.layout.browsing_list_item_layout, null);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Should never happen.
|
||||||
|
throw new RuntimeException("Unknown view type: " + viewType);
|
||||||
|
}
|
||||||
|
|
||||||
|
reuseView.setTag(new ViewHolder(reuseView));
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewHolder holder = (ViewHolder) reuseView.getTag();
|
||||||
|
|
||||||
|
if (viewType == LOADING_ROW) {
|
||||||
|
return reuseView;
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.bindView(mFlatRows.get(position));
|
||||||
|
|
||||||
|
return reuseView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled(int position) {
|
||||||
|
if (position == mFlatRows.size() && mIsLoadingData) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !mFlatRows.get(position).isSectionHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyDataSetChanged() {
|
||||||
|
flattenData();
|
||||||
|
|
||||||
|
super.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flattenData() {
|
||||||
|
mFlatRows.clear();
|
||||||
|
|
||||||
|
int rowsCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < getSectionCount(); i++) {
|
||||||
|
rowsCount = addFlatRow(getNameForSection(i), getIconForSection(i), true, rowsCount);
|
||||||
|
|
||||||
|
for (int j = 0; j < getSizeForSection(i); j++) {
|
||||||
|
rowsCount = addFlatRow(getNameForRow(i, j), getIconForRow(i, j), false, rowsCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int addFlatRow(String name, int iconId, boolean isSectionHeader, int position) {
|
||||||
|
FlatRow newRow = FlatRow.create(
|
||||||
|
name,
|
||||||
|
iconId,
|
||||||
|
isSectionHeader,
|
||||||
|
position < mFlatRows.size() ? mFlatRows.get(position) : null);
|
||||||
|
|
||||||
|
|
||||||
|
if (position < mFlatRows.size()) {
|
||||||
|
mFlatRows.set(position, newRow);
|
||||||
|
} else {
|
||||||
|
mFlatRows.add(newRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
return position + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isLoadingData() {
|
||||||
|
return mIsLoadingData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ViewHolder {
|
||||||
|
private ImageView mIconImageView;
|
||||||
|
private TextView mNameTextView;
|
||||||
|
|
||||||
|
ViewHolder(View parent) {
|
||||||
|
mIconImageView = parent.findViewById(R.id.browsing_icon);
|
||||||
|
mNameTextView = parent.findViewById(R.id.browsing_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bindView(FlatRow row) {
|
||||||
|
if (mIconImageView == null || mNameTextView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.isSectionHeader()) {
|
||||||
|
mIconImageView.setPadding(8, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
mIconImageView.setImageResource(row.getIconId());
|
||||||
|
mNameTextView.setText(row.getName());
|
||||||
|
|
||||||
|
if (row.isSectionHeader()) {
|
||||||
|
mIconImageView.setPadding(0, 0, 0, 0);
|
||||||
|
mNameTextView.setTypeface(null, Typeface.BOLD);
|
||||||
|
} else {
|
||||||
|
mIconImageView.setPadding(16, 0, 0, 0);
|
||||||
|
mNameTextView.setTypeface(null, Typeface.NORMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FlatRow {
|
||||||
|
private String mName;
|
||||||
|
private int mIconId;
|
||||||
|
private boolean mIsSectionHeader;
|
||||||
|
|
||||||
|
private FlatRow(String name, int iconId, boolean isSectionHeader) {
|
||||||
|
mName = name;
|
||||||
|
mIconId = iconId;
|
||||||
|
mIsSectionHeader = isSectionHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FlatRow create(String name, int iconId, boolean isSectionHeader, FlatRow reuse) {
|
||||||
|
if (reuse == null) {
|
||||||
|
return new FlatRow(name, iconId, isSectionHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
reuse.mName = name;
|
||||||
|
reuse.mIconId = iconId;
|
||||||
|
reuse.mIsSectionHeader = isSectionHeader;
|
||||||
|
|
||||||
|
return reuse;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getName() {
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getIconId() {
|
||||||
|
return mIconId;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isSectionHeader() {
|
||||||
|
return mIsSectionHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,6 @@ 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;
|
||||||
|
@ -59,7 +58,6 @@ 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.mount.MountServerActivity;
|
|
||||||
import com.google.android.sambadocumentsprovider.nativefacade.SmbFacade;
|
import com.google.android.sambadocumentsprovider.nativefacade.SmbFacade;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
@ -131,22 +129,6 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final OnTaskFinishedCallback<List<String>> mLoadSharesFinishedCallback =
|
|
||||||
new OnTaskFinishedCallback<List<String>>() {
|
|
||||||
@Override
|
|
||||||
public void onTaskFinished(
|
|
||||||
@OnTaskFinishedCallback.Status int status,
|
|
||||||
@Nullable List<String> item,
|
|
||||||
@Nullable Exception exception) {
|
|
||||||
if (BuildConfig.DEBUG) Log.d(TAG, "Browsing callback");
|
|
||||||
|
|
||||||
mBrowsingStorage = item;
|
|
||||||
|
|
||||||
getContext().getContentResolver().notifyChange(
|
|
||||||
toNotifyUri(toUri(NetworkBrowser.SMB_BROWSING_URI.toString())), null, false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final MountedShareChangeListener mShareChangeListener = new MountedShareChangeListener() {
|
private final MountedShareChangeListener mShareChangeListener = new MountedShareChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onMountedServerChange() {
|
public void onMountedServerChange() {
|
||||||
|
@ -162,7 +144,6 @@ 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;
|
private List<String> mBrowsingStorage;
|
||||||
|
|
||||||
|
@ -178,7 +159,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
@ -190,14 +170,6 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
|
|
||||||
MatrixCursor cursor = new MatrixCursor(projection);
|
MatrixCursor cursor = new MatrixCursor(projection);
|
||||||
|
|
||||||
cursor.addRow(new Object[] {
|
|
||||||
NetworkBrowser.SMB_BROWSING_URI.toString(),
|
|
||||||
NetworkBrowser.SMB_BROWSING_URI.toString(),
|
|
||||||
getContext().getResources().getString(R.string.browsing_root_name),
|
|
||||||
0,
|
|
||||||
R.drawable.ic_cloud,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (String uri : mShareManager) {
|
for (String uri : mShareManager) {
|
||||||
if (!mShareManager.isShareMounted(uri)) {
|
if (!mShareManager.isShareMounted(uri)) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -253,12 +225,6 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
final MatrixCursor cursor = new MatrixCursor(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)) {
|
||||||
|
|
||||||
|
@ -292,10 +258,6 @@ 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 {
|
||||||
|
@ -439,68 +401,6 @@ public class SambaDocumentsProvider extends DocumentsProvider {
|
||||||
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 == null) {
|
|
||||||
AsyncTask serversTask = mNetworkBrowser.getServersAsync(mLoadSharesFinishedCallback);
|
|
||||||
|
|
||||||
Bundle extra = new Bundle();
|
|
||||||
extra.putBoolean(DocumentsContract.EXTRA_LOADING, true);
|
|
||||||
|
|
||||||
cursor.setNotificationUri(getContext().getContentResolver(), toNotifyUri(uri));
|
|
||||||
cursor.setExtras(extra);
|
|
||||||
cursor.setLoadingTask(serversTask);
|
|
||||||
} else {
|
|
||||||
for (String server : mBrowsingStorage) {
|
|
||||||
cursor.addRow(getCursorRowForServer(projection, server));
|
|
||||||
}
|
|
||||||
|
|
||||||
mBrowsingStorage = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String createDocument(String parentDocumentId, String mimeType, String displayName)
|
public String createDocument(String parentDocumentId, String mimeType, String displayName)
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
|
|
|
@ -21,8 +21,13 @@
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
|
||||||
android:fillColor="#646464"
|
<path
|
||||||
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
|
android:fillColor="#000000"
|
||||||
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"/>
|
android:pathData="M20 5H4c-1.1 0-1.99 .9 -1.99 2L2 17c0 1.1 .9 2 2 2h16c1.1 0 2-.9
|
||||||
</vector>
|
2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2V8zm0 3h2v2h-2v-2zM8 8h2v2H8V8zm0
|
||||||
|
3h2v2H8v-2zm-1 2H5v-2h2v2zm0-3H5V8h2v2zm9
|
||||||
|
7H8v-2h8v2zm0-4h-2v-2h2v2zm0-3h-2V8h2v2zm3 3h-2v-2h2v2zm0-3h-2V8h2v2z" />
|
||||||
|
<path
|
||||||
|
android:pathData="M0 0h24v24H0zm0 0h24v24H0z" />
|
||||||
|
</vector>
|
|
@ -40,7 +40,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<EditText
|
<com.google.android.sambadocumentsprovider.mount.BrowsingAutocompleteTextView
|
||||||
android:id="@+id/share_path"
|
android:id="@+id/share_path"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/clickable_height"
|
android:layout_height="@dimen/clickable_height"
|
||||||
|
@ -53,7 +53,6 @@
|
||||||
android:id="@+id/needs_password"
|
android:id="@+id/needs_password"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/clickable_height"
|
android:layout_height="@dimen/clickable_height"
|
||||||
android:layout_below="@id/share_path"
|
|
||||||
android:checked="false"
|
android:checked="false"
|
||||||
android:text="@string/needs_password"/>
|
android:text="@string/needs_password"/>
|
||||||
|
|
||||||
|
|
42
app/src/main/res/layout/browsing_list_item_layout.xml
Normal file
42
app/src/main/res/layout/browsing_list_item_layout.xml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2017 Google Inc.
|
||||||
|
~
|
||||||
|
~ This program is free software: you can redistribute it and/or modify
|
||||||
|
~ it under the terms of the GNU General Public License as published by
|
||||||
|
~ the Free Software Foundation, either version 3 of the License, or
|
||||||
|
~ (at your option) any later version.
|
||||||
|
~
|
||||||
|
~ This program is distributed in the hope that it will be useful,
|
||||||
|
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
~ GNU General Public License for more details.
|
||||||
|
~
|
||||||
|
~ You should have received a copy of the GNU General Public License
|
||||||
|
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/browsing_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="@drawable/ic_server"
|
||||||
|
android:layout_marginStart="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/browsing_name"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
36
app/src/main/res/layout/browsing_progress_item_layout.xml
Normal file
36
app/src/main/res/layout/browsing_progress_item_layout.xml
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2017 Google Inc.
|
||||||
|
~
|
||||||
|
~ This program is free software: you can redistribute it and/or modify
|
||||||
|
~ it under the terms of the GNU General Public License as published by
|
||||||
|
~ the Free Software Foundation, either version 3 of the License, or
|
||||||
|
~ (at your option) any later version.
|
||||||
|
~
|
||||||
|
~ This program is distributed in the hope that it will be useful,
|
||||||
|
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
~ GNU General Public License for more details.
|
||||||
|
~
|
||||||
|
~ You should have received a copy of the GNU General Public License
|
||||||
|
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_margin="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:text="@string/browsing_waiting_prompt"/>
|
||||||
|
</LinearLayout>
|
|
@ -49,6 +49,7 @@
|
||||||
<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>
|
<string name="browsing_root_name">Samba Shares</string>
|
||||||
|
<string name="browsing_waiting_prompt">Looking for shares…</string>
|
||||||
|
|
||||||
<string name="pin_this_share">Pin this share</string>
|
<string name="pin_this_share">Pin this share</string>
|
||||||
<string name="login">Login</string>
|
<string name="login">Login</string>
|
||||||
|
|
Loading…
Reference in a new issue