diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 9baa18993..7e200215d 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -50,13 +50,6 @@
android:name=".gui.RepoSettingsActivity" />
-
-
-
diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/FolderPickerActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/FolderPickerActivity.java
deleted file mode 100644
index 802852e16..000000000
--- a/src/main/java/com/nutomic/syncthingandroid/gui/FolderPickerActivity.java
+++ /dev/null
@@ -1,217 +0,0 @@
-package com.nutomic.syncthingandroid.gui;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.IBinder;
-import android.support.v7.app.ActionBarActivity;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.EditText;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.nutomic.syncthingandroid.R;
-import com.nutomic.syncthingandroid.syncthing.SyncthingService;
-import com.nutomic.syncthingandroid.syncthing.SyncthingServiceBinder;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Stack;
-
-/**
- * Activity that allows selecting a directory in the local file system.
- */
-public class FolderPickerActivity extends ActionBarActivity
- implements AdapterView.OnItemClickListener, SyncthingService.OnApiChangeListener {
-
- private static final String TAG = "FolderPickerActivity";
-
- public static final String EXTRA_INITIAL_DIRECTORY = "initial_directory";
-
- public static final String EXTRA_RESULT_DIRECTORY = "result_directory";
-
- private ListView mListView;
-
- private FileAdapter mAdapter;
-
- private File mLocation;
-
- private SyncthingService mSyncthingService;
-
- private final ServiceConnection mSyncthingServiceConnection = new ServiceConnection() {
-
- public void onServiceConnected(ComponentName className, IBinder service) {
- SyncthingServiceBinder binder = (SyncthingServiceBinder) service;
- mSyncthingService = binder.getService();
- mSyncthingService.registerOnApiChangeListener(FolderPickerActivity.this);
- }
-
- public void onServiceDisconnected(ComponentName className) {
- mSyncthingService = null;
- }
- };
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-
- setContentView(R.layout.folder_picker_activity);
- mListView = (ListView) findViewById(android.R.id.list);
- mListView.setOnItemClickListener(this);
- mListView.setEmptyView(findViewById(android.R.id.empty));
- mAdapter = new FileAdapter(this);
- mListView.setAdapter(mAdapter);
-
- mLocation = new File(getIntent().getStringExtra(EXTRA_INITIAL_DIRECTORY));
- refresh();
-
- bindService(new Intent(this, SyncthingService.class),
- mSyncthingServiceConnection, Context.BIND_AUTO_CREATE);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- unbindService(mSyncthingServiceConnection);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.folder_picker, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.create_folder:
- final EditText et = new EditText(this);
- AlertDialog dialog = new AlertDialog.Builder(this)
- .setTitle(R.string.create_folder)
- .setView(et)
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialogInterface, int i) {
- createFolder(et.getText().toString());
- }
- })
- .setNegativeButton(android.R.string.cancel, null)
- .create();
- dialog.setOnShowListener(new DialogInterface.OnShowListener() {
- @Override
- public void onShow(DialogInterface dialogInterface) {
- ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE))
- .showSoftInput(et, InputMethodManager.SHOW_IMPLICIT);
- }
- });
- dialog.show();
- return true;
- case R.id.select:
- Intent intent = new Intent()
- .putExtra(EXTRA_RESULT_DIRECTORY, mLocation.getAbsolutePath());
- setResult(Activity.RESULT_OK, intent);
- finish();
- return true;
- case android.R.id.home:
- finish();
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- /**
- * Creates a new folder with the given name and enters it.
- */
- private void createFolder(String name) {
- File newFolder = new File(mLocation, name);
- newFolder.mkdir();
- mLocation = newFolder;
- refresh();
- }
-
- /**
- * Refreshes the ListView to show the contents of the folder in {@code }mLocation.peek()}.
- */
- private void refresh() {
- mAdapter.clear();
- File[] contents = mLocation.listFiles(new FileFilter() {
- @Override
- public boolean accept(File file) {
- return file.isDirectory();
- }
- });
- Arrays.sort(contents);
- for (File f : contents) {
- mAdapter.add(f);
- }
- }
-
- @Override
- public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
- mLocation = mAdapter.getItem(i);
- refresh();
- }
-
- private class FileAdapter extends ArrayAdapter {
-
- public FileAdapter(Context context) {
- super(context, android.R.layout.simple_list_item_1);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- LayoutInflater inflater = (LayoutInflater) getContext()
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- convertView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
- }
-
- TextView title = (TextView) convertView.findViewById(android.R.id.text1);
- title.setText(getItem(position).getName());
- return convertView;
- }
- }
-
- @Override
- public void onBackPressed() {
- if (!mLocation.equals(Environment.getExternalStorageDirectory())) {
- mLocation = mLocation.getParentFile();
- refresh();
- }
- else {
- setResult(Activity.RESULT_CANCELED);
- finish();
- }
- }
-
- @Override
- public void onApiChange(boolean isAvailable) {
- if (!isAvailable) {
- setResult(Activity.RESULT_CANCELED);
- finish();
- }
- }
-
-}
diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java
index e5d6e2e20..50f0e5368 100644
--- a/src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java
+++ b/src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java
@@ -5,19 +5,25 @@
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName;
+import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.database.Cursor;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
+import android.os.storage.StorageManager;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
@@ -28,7 +34,9 @@
import com.nutomic.syncthingandroid.syncthing.SyncthingServiceBinder;
import com.nutomic.syncthingandroid.util.ExtendedCheckBoxPreference;
+import java.io.File;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
/**
@@ -279,15 +287,22 @@ else if (preference.equals(mVersioningKeep)) {
@Override
public boolean onPreferenceClick(Preference preference) {
if (preference.equals(mDirectory)) {
- Intent intent = new Intent(this, FolderPickerActivity.class)
- .putExtra(FolderPickerActivity.EXTRA_INITIAL_DIRECTORY,
- (mRepo.Directory.length() != 0)
- ? mRepo.Directory
- : Environment.getExternalStorageDirectory().getAbsolutePath());
+ Intent intent;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+ }
+ else {
+ intent = new Intent(Intent.ACTION_GET_CONTENT);
+ }
+ intent.addCategory(Intent.CATEGORY_OPENABLE)
+ .setType(DocumentsContract.Document.MIME_TYPE_DIR)
+ .putExtra(Intent.EXTRA_LOCAL_ONLY, true);
startActivityForResult(intent, DIRECTORY_REQUEST_CODE);
+ return true;
}
else if (preference.equals(mNodes) && mSyncthingService.getApi().getNodes().isEmpty()) {
Toast.makeText(this, R.string.no_nodes, Toast.LENGTH_SHORT).show();
+ return true;
}
else if (preference.equals(mDelete)) {
new AlertDialog.Builder(this)
@@ -308,10 +323,20 @@ public void onClick(DialogInterface dialogInterface, int i) {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (resultCode == Activity.RESULT_OK && requestCode == DIRECTORY_REQUEST_CODE) {
- mRepo.Directory = data.getStringExtra(FolderPickerActivity.EXTRA_RESULT_DIRECTORY);
+ if (requestCode == DIRECTORY_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+ Uri uri = data.getData();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ getContentResolver().takePersistableUriPermission(uri, data.getFlags()
+ & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION));
+ //mRepo.Directory = getPath(uri);
+ mRepo.Directory = uri.toString();
+ }
+ else {
+ // TODO: properly get path (content:// URI)
+ mRepo.Directory = uri.getPath();
+ }
mDirectory.setSummary(mRepo.Directory);
- repoUpdated();
}
}
@@ -321,4 +346,141 @@ private void repoUpdated() {
}
}
+ /**
+ * Get a file path from a Uri. This will get the the path for Storage Access
+ * Framework Documents, as well as the _data field for the MediaStore and
+ * other file-based ContentProviders.
+ *
+ * Callers should check whether the path is local before assuming it
+ * represents a local file.
+ *
+ * @param uri The Uri to query.
+ * @author paulburke
+ */
+ @TargetApi(19)
+ public String getPath(final Uri uri) {
+
+ // DocumentProvider
+ if (DocumentsContract.isDocumentUri(this, uri)) {
+ // ExternalStorageProvider
+ if (isExternalStorageDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+
+ if ("primary".equalsIgnoreCase(split[0])) {
+ return Environment.getExternalStorageDirectory() + "/" + split[1];
+ }
+
+ // TODO handle non-primary volumes
+ }
+ // DownloadsProvider
+ else if (isDownloadsDocument(uri)) {
+
+ final String id = DocumentsContract.getDocumentId(uri);
+ final Uri contentUri = ContentUris.withAppendedId(
+ Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+
+ return getDataColumn(contentUri, null, null);
+ }
+ // MediaProvider
+ else if (isMediaDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ Uri contentUri = null;
+ if ("image".equals(type)) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else if ("video".equals(type)) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ } else if ("audio".equals(type)) {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ }
+
+ final String selection = "_id=?";
+ final String[] selectionArgs = new String[] {
+ split[1]
+ };
+
+ return getDataColumn(contentUri, selection, selectionArgs);
+ }
+ }
+ // MediaStore (and general)
+ else if ("content".equalsIgnoreCase(uri.getScheme())) {
+
+ // Return the remote address
+ if (isGooglePhotosUri(uri))
+ return uri.getLastPathSegment();
+
+ return getDataColumn(uri, null, null);
+ }
+ // File
+ else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ return uri.getPath();
+ }
+
+ return null;
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is ExternalStorageProvider.
+ * @author paulburke
+ */
+ public static boolean isExternalStorageDocument(Uri uri) {
+ return "com.android.externalstorage.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is DownloadsProvider.
+ * @author paulburke
+ */
+ public static boolean isDownloadsDocument(Uri uri) {
+ return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is MediaProvider.
+ * @author paulburke
+ */
+ public static boolean isMediaDocument(Uri uri) {
+ return "com.android.providers.media.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is Google Photos.
+ */
+ public static boolean isGooglePhotosUri(Uri uri) {
+ return "com.google.android.apps.photos.content".equals(uri.getAuthority());
+ }/**
+ * Get the value of the data column for this Uri. This is useful for
+ * MediaStore Uris, and other file-based ContentProviders.
+ *
+ * @param uri The Uri to query.
+ * @param selection (Optional) Filter used in the query.
+ * @param selectionArgs (Optional) Selection arguments used in the query.
+ * @return The value of the _data column, which is typically a file path.
+ * @author paulburke
+ */
+ public String getDataColumn(Uri uri, String selection,
+ String[] selectionArgs) {
+ Cursor cursor = null;
+
+ try {
+ cursor = getContentResolver().query(uri, new String[]{"_data"}, selection, selectionArgs,
+ null);
+ if (cursor != null && cursor.moveToFirst()) {
+ final int column_index = cursor.getColumnIndexOrThrow("_data");
+ return cursor.getString(column_index);
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ return null;
+ }
+
}
diff --git a/src/main/res/layout/folder_picker_activity.xml b/src/main/res/layout/folder_picker_activity.xml
deleted file mode 100644
index 83c003de5..000000000
--- a/src/main/res/layout/folder_picker_activity.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/main/res/menu/folder_picker.xml b/src/main/res/menu/folder_picker.xml
deleted file mode 100644
index 399a07da2..000000000
--- a/src/main/res/menu/folder_picker.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index fd126763f..52bcd329b 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -200,21 +200,6 @@ Please report any problems you encounter.
Syncthing Version
-
-
-
-
- Folder Picker
-
-
- Directory is Empty
-
-
- Create new Folder
-
-
- Select Folder
-