diff --git a/src/main/java/com/nutomic/syncthingandroid/activities/FolderActivity.java b/src/main/java/com/nutomic/syncthingandroid/activities/FolderActivity.java index eee8ac2b..72dc3bf8 100644 --- a/src/main/java/com/nutomic/syncthingandroid/activities/FolderActivity.java +++ b/src/main/java/com/nutomic/syncthingandroid/activities/FolderActivity.java @@ -141,7 +141,7 @@ public class FolderActivity extends SyncthingActivity mEditIgnores = findViewById(R.id.edit_ignores); mPathView.setOnClickListener(view -> - startActivityForResult(FolderPickerActivity.createIntent(this, mFolder.path), FolderPickerActivity.DIRECTORY_REQUEST_CODE)); + startActivityForResult(FolderPickerActivity.createIntent(this, mFolder.path, null), FolderPickerActivity.DIRECTORY_REQUEST_CODE)); findViewById(R.id.versioningContainer).setOnClickListener(v -> showVersioningDialog()); mEditIgnores.setOnClickListener(v -> editIgnores()); diff --git a/src/main/java/com/nutomic/syncthingandroid/activities/FolderPickerActivity.java b/src/main/java/com/nutomic/syncthingandroid/activities/FolderPickerActivity.java index 444b1474..1f7a6dbb 100644 --- a/src/main/java/com/nutomic/syncthingandroid/activities/FolderPickerActivity.java +++ b/src/main/java/com/nutomic/syncthingandroid/activities/FolderPickerActivity.java @@ -11,9 +11,12 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.IBinder; +import android.preference.PreferenceManager; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.text.TextUtils; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -30,6 +33,9 @@ import com.google.common.collect.Sets; import com.nutomic.syncthingandroid.R; import com.nutomic.syncthingandroid.SyncthingApp; import com.nutomic.syncthingandroid.service.SyncthingService; +import com.nutomic.syncthingandroid.util.Util; + +import org.w3c.dom.Text; import java.io.File; import java.util.ArrayList; @@ -48,6 +54,9 @@ public class FolderPickerActivity extends SyncthingActivity private static final String EXTRA_INITIAL_DIRECTORY = "com.nutomic.syncthingandroid.activities.FolderPickerActivity.INITIAL_DIRECTORY"; + public static final String EXTRA_ROOT_DIRECTORY = + "com.nutomic.syncthingandroid.activities.FolderPickerActivity.ROOT_DIRECTORY"; + public static final String EXTRA_RESULT_DIRECTORY = "com.nutomic.syncthingandroid.activities.FolderPickerActivity.RESULT_DIRECTORY"; @@ -56,18 +65,21 @@ public class FolderPickerActivity extends SyncthingActivity private ListView mListView; private FileAdapter mFilesAdapter; private RootsAdapter mRootsAdapter; - @Inject SharedPreferences mPreferences; /** * Location of null means that the list of roots is displayed. */ private File mLocation; - public static Intent createIntent(Context context, String currentPath) { + public static Intent createIntent(Context context, String initialDirectory, @Nullable String rootDirectory) { Intent intent = new Intent(context, FolderPickerActivity.class); - if (!TextUtils.isEmpty(currentPath)) { - intent.putExtra(FolderPickerActivity.EXTRA_INITIAL_DIRECTORY, currentPath); + if (!TextUtils.isEmpty(initialDirectory)) { + intent.putExtra(EXTRA_INITIAL_DIRECTORY, initialDirectory); + } + + if (!TextUtils.isEmpty(rootDirectory)) { + intent.putExtra(EXTRA_ROOT_DIRECTORY, rootDirectory); } return intent; @@ -101,7 +113,8 @@ public class FolderPickerActivity extends SyncthingActivity } /** - * Reads available storage devices/folders from various APIs and inserts them into + * If a root directory is specified it is added to {@link #mRootsAdapter} otherwise + * all available storage devices/folders from various APIs are inserted into * {@link #mRootsAdapter}. */ @SuppressLint("NewApi") @@ -111,22 +124,28 @@ public class FolderPickerActivity extends SyncthingActivity roots.addAll(Arrays.asList(getExternalFilesDirs(null))); roots.remove(getExternalFilesDir(null)); } - roots.add(Environment.getExternalStorageDirectory()); - roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)); - roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)); - roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)); - roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)); - roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)); - } - // Add paths that might not be accessible to Syncthing. - if (mPreferences.getBoolean("advanced_folder_picker", false)) { - Collections.addAll(roots, new File("/storage/").listFiles()); - roots.add(new File("/")); - } + String rootDir = getIntent().getStringExtra(EXTRA_ROOT_DIRECTORY); + if (getIntent().hasExtra(EXTRA_ROOT_DIRECTORY) && !TextUtils.isEmpty(rootDir)) { + roots.add(new File(rootDir)); + } else { + roots.add(Environment.getExternalStorageDirectory()); + roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)); + roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)); + roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)); + roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)); + roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)); + } + // Add paths that might not be accessible to Syncthing. + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + if (sp.getBoolean("advanced_folder_picker", false)) { + Collections.addAll(roots, new File("/storage/").listFiles()); + roots.add(new File("/")); + } + } // Remove any invalid directories. Iterator it = roots.iterator(); while (it.hasNext()) { @@ -135,6 +154,7 @@ public class FolderPickerActivity extends SyncthingActivity it.remove(); } } + mRootsAdapter.addAll(Sets.newTreeSet(roots)); } @@ -179,7 +199,7 @@ public class FolderPickerActivity extends SyncthingActivity return true; case R.id.select: Intent intent = new Intent() - .putExtra(EXTRA_RESULT_DIRECTORY, mLocation.getAbsolutePath()); + .putExtra(EXTRA_RESULT_DIRECTORY, Util.formatPath(mLocation.getAbsolutePath())); setResult(Activity.RESULT_OK, intent); finish(); return true; @@ -291,7 +311,7 @@ public class FolderPickerActivity extends SyncthingActivity /** * Goes up a directory, up to the list of roots if there are multiple roots. - * + *

* If we already are in the list of roots, or if we are directly in the only * root folder, we cancel. */ diff --git a/src/main/java/com/nutomic/syncthingandroid/activities/ShareActivity.java b/src/main/java/com/nutomic/syncthingandroid/activities/ShareActivity.java index e34022f7..c58ed20c 100644 --- a/src/main/java/com/nutomic/syncthingandroid/activities/ShareActivity.java +++ b/src/main/java/com/nutomic/syncthingandroid/activities/ShareActivity.java @@ -7,10 +7,13 @@ import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.preference.PreferenceManager; import android.provider.MediaStore; import android.text.TextUtils; import android.util.Log; +import android.view.View; import android.webkit.MimeTypeMap; +import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; @@ -37,7 +40,7 @@ import java.util.Map; /** * Shares incoming files to syncthing folders. - * + *

* {@link #getDisplayNameForUri} and {@link #getDisplayNameFromContentResolver} are taken from * ownCloud Android {@see https://github.com/owncloud/android/blob/79664304fdb762b2e04f1ac505f50d0923ddd212/src/com/owncloud/android/utils/UriUtils.java#L193} */ @@ -45,6 +48,13 @@ public class ShareActivity extends StateDialogActivity implements SyncthingActivity.OnServiceConnectedListener, SyncthingService.OnApiChangeListener { private static final String TAG = "ShareActivity"; + private static final String PREF_PREVIOUSLY_SELECTED_SYNCTHING_FOLDER = "previously_selected_syncthing_folder"; + + public static final String PREF_FOLDER_SAVED_SUBDIRECTORY = "saved_sub_directory_"; + + private TextView mSubDirectoryTextView; + + private Spinner mFoldersSpinner; @Override public void onApiChange(SyncthingService.State currentState) { @@ -53,12 +63,24 @@ public class ShareActivity extends StateDialogActivity List folders = getApi().getFolders(); + // Get the index of the previously selected folder. + int folderIndex = 0; + String savedFolderId = PreferenceManager.getDefaultSharedPreferences(this) + .getString(PREF_PREVIOUSLY_SELECTED_SYNCTHING_FOLDER, ""); + for (Folder folder : folders) { + if (folder.id.equals(savedFolderId)) { + folderIndex = folders.indexOf(folder); + break; + } + } + ArrayAdapter adapter = new ArrayAdapter<>( this, android.R.layout.simple_spinner_item, folders); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); Spinner sItems = findViewById(R.id.folders); sItems.setAdapter(adapter); + sItems.setSelection(folderIndex); } @Override @@ -81,12 +103,15 @@ public class ShareActivity extends StateDialogActivity registerOnServiceConnectedListener(this); - Spinner mFoldersSpinner = findViewById(R.id.folders); Button mShareButton = findViewById(R.id.share_button); Button mCancelButton = findViewById(R.id.cancel_button); + Button browseButton = findViewById(R.id.browse_button); EditText mShareName = findViewById(R.id.name); TextView mShareTitle = findViewById(R.id.namesTitle); + mSubDirectoryTextView = findViewById(R.id.sub_directory_Textview); + mFoldersSpinner = findViewById(R.id.folders); + // TODO: add support for EXTRA_TEXT (notes, memos sharing) ArrayList extrasToCopy = new ArrayList<>(); if (getIntent().getAction().equals(Intent.ACTION_SEND)) { @@ -124,9 +149,32 @@ public class ShareActivity extends StateDialogActivity if (files.size() == 1) files.entrySet().iterator().next().setValue(mShareName.getText().toString()); Folder folder = (Folder) mFoldersSpinner.getSelectedItem(); - new CopyFilesTask(files, folder).execute(); + File directory = new File(folder.path, getSavedSubDirectory()); + new CopyFilesTask(files, folder, directory).execute(); }); + + mFoldersSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + mSubDirectoryTextView.setText(getSavedSubDirectory()); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + browseButton.setOnClickListener(view -> { + Folder folder = (Folder) mFoldersSpinner.getSelectedItem(); + File initialDirectory = new File(folder.path, getSavedSubDirectory()); + startActivityForResult(FolderPickerActivity.createIntent(getApplicationContext(), + initialDirectory.getAbsolutePath(), folder.path), + FolderPickerActivity.DIRECTORY_REQUEST_CODE); + }); + mCancelButton.setOnClickListener(view -> finish()); + mSubDirectoryTextView.setText(getSavedSubDirectory()); } /** @@ -209,15 +257,32 @@ public class ShareActivity extends StateDialogActivity return displayName; } + /** + * Get the previously selected sub directory for the currently selected Syncthing folder. + */ + private String getSavedSubDirectory() { + Folder selectedFolder = (Folder) mFoldersSpinner.getSelectedItem(); + String savedSubDirectory = ""; + + if (selectedFolder != null) { + savedSubDirectory = PreferenceManager.getDefaultSharedPreferences(this) + .getString(PREF_FOLDER_SAVED_SUBDIRECTORY + selectedFolder.id, ""); + } + + return savedSubDirectory; + } + private class CopyFilesTask extends AsyncTask { private ProgressDialog mProgress; - private final Map mFiles; - private final Folder mFolder; + private Map mFiles; + private Folder mFolder; + private File mDirectory; private int mCopied = 0, mIgnored = 0; - CopyFilesTask(Map files, Folder folder) { + CopyFilesTask(Map files, Folder folder, File directory) { this.mFiles = files; this.mFolder = folder; + this.mDirectory = directory; } protected void onPreExecute() { @@ -230,7 +295,7 @@ public class ShareActivity extends StateDialogActivity for (Map.Entry entry : mFiles.entrySet()) { InputStream inputStream = null; try { - File outFile = new File(mFolder.path, entry.getValue()); + File outFile = new File(mDirectory, entry.getValue()); if (outFile.isFile()) { mIgnored++; continue; @@ -261,10 +326,10 @@ public class ShareActivity extends StateDialogActivity protected void onPostExecute(Boolean isError) { Util.dismissDialogSafe(mProgress, ShareActivity.this); Toast.makeText(ShareActivity.this, mIgnored > 0 ? - getResources().getQuantityString(R.plurals.copy_success_partially, mCopied, - mCopied, mFolder.label, mIgnored) : - getResources().getQuantityString(R.plurals.copy_success, mCopied, mCopied, - mFolder.label), + getResources().getQuantityString(R.plurals.copy_success_partially, mCopied, + mCopied, mFolder.label, mIgnored) : + getResources().getQuantityString(R.plurals.copy_success, mCopied, mCopied, + mFolder.label), Toast.LENGTH_LONG).show(); if (isError) { Toast.makeText(ShareActivity.this, getString(R.string.copy_exception), @@ -273,4 +338,32 @@ public class ShareActivity extends StateDialogActivity finish(); } } + + @Override + protected void onPause() { + super.onPause(); + if (mFoldersSpinner.getSelectedItem() != null) { + Folder selectedFolder = (Folder) mFoldersSpinner.getSelectedItem(); + PreferenceManager.getDefaultSharedPreferences(this).edit() + .putString(PREF_PREVIOUSLY_SELECTED_SYNCTHING_FOLDER, selectedFolder.id) + .apply(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == FolderPickerActivity.DIRECTORY_REQUEST_CODE && resultCode == RESULT_OK) { + Folder selectedFolder = (Folder) mFoldersSpinner.getSelectedItem(); + String folderDirectory = Util.formatPath(selectedFolder.path); + String subDirectory = data.getStringExtra(FolderPickerActivity.EXTRA_RESULT_DIRECTORY); + //Remove the parent directory from the string, so it is only the Sub directory that is displayed to the user. + subDirectory = subDirectory.replace(folderDirectory, ""); + mSubDirectoryTextView.setText(subDirectory); + + PreferenceManager.getDefaultSharedPreferences(this) + .edit().putString(PREF_FOLDER_SAVED_SUBDIRECTORY + selectedFolder.id, subDirectory) + .apply(); + } + } } diff --git a/src/main/java/com/nutomic/syncthingandroid/fragments/dialog/StaggeredVersioningFragment.java b/src/main/java/com/nutomic/syncthingandroid/fragments/dialog/StaggeredVersioningFragment.java index f6bc4800..5e472fac 100644 --- a/src/main/java/com/nutomic/syncthingandroid/fragments/dialog/StaggeredVersioningFragment.java +++ b/src/main/java/com/nutomic/syncthingandroid/fragments/dialog/StaggeredVersioningFragment.java @@ -65,7 +65,7 @@ public class StaggeredVersioningFragment extends Fragment { mPathView.setText(currentPath); mPathView.setOnClickListener(view -> - startActivityForResult(FolderPickerActivity.createIntent(getContext(), currentPath), FolderPickerActivity.DIRECTORY_REQUEST_CODE)); + startActivityForResult(FolderPickerActivity.createIntent(getContext(), currentPath, null), FolderPickerActivity.DIRECTORY_REQUEST_CODE)); } @Override diff --git a/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java b/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java index 2a4e8f52..878b8aa5 100644 --- a/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java +++ b/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java @@ -3,6 +3,7 @@ package com.nutomic.syncthingandroid.service; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.preference.PreferenceManager; import android.util.Log; import com.google.common.base.Objects; @@ -18,6 +19,7 @@ import com.google.gson.JsonParser; import com.nutomic.syncthingandroid.BuildConfig; import com.nutomic.syncthingandroid.SyncthingApp; import com.nutomic.syncthingandroid.activities.RestartActivity; +import com.nutomic.syncthingandroid.activities.ShareActivity; import com.nutomic.syncthingandroid.http.GetRequest; import com.nutomic.syncthingandroid.http.PostConfigRequest; import com.nutomic.syncthingandroid.http.PostScanRequest; @@ -228,6 +230,10 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener, public void removeFolder(String id) { removeFolderInternal(id); sendConfig(); + // Remove saved data from share activity for this folder. + PreferenceManager.getDefaultSharedPreferences(mContext).edit() + .remove(ShareActivity.PREF_FOLDER_SAVED_SUBDIRECTORY+id) + .apply(); } private void removeFolderInternal(String id) { diff --git a/src/main/java/com/nutomic/syncthingandroid/util/Util.java b/src/main/java/com/nutomic/syncthingandroid/util/Util.java index 2b8a0527..90971bb5 100644 --- a/src/main/java/com/nutomic/syncthingandroid/util/Util.java +++ b/src/main/java/com/nutomic/syncthingandroid/util/Util.java @@ -15,6 +15,7 @@ import com.nutomic.syncthingandroid.R; import java.io.DataOutputStream; import java.io.IOException; +import java.io.File; import java.text.DecimalFormat; import eu.chainfire.libsuperuser.Shell; @@ -42,7 +43,7 @@ public class Util { /** * Converts a number of bytes to a human readable file size (eg 3.5 GiB). - * + *

* Based on http://stackoverflow.com/a/5599842 */ public static String readableFileSize(Context context, long bytes) { @@ -56,7 +57,7 @@ public class Util { /** * Converts a number of bytes to a human readable transfer rate in bytes per second * (eg 100 KiB/s). - * + *

* Based on http://stackoverflow.com/a/5599842 */ public static String readableTransferRate(Context context, long bits) { @@ -69,13 +70,14 @@ public class Util { } /** + * <<<<<<< HEAD * Normally an application's data directory is only accessible by the corresponding application. * Therefore, every file and directory is owned by an application's user and group. When running Syncthing as root, * it writes to the application's data directory. This leaves files and directories behind which are owned by root having 0600. * Moreover, those acitons performed as root changes a file's type in terms of SELinux. * A subsequent start of Syncthing will fail due to insufficient permissions. * Hence, this method fixes the owner, group and the files' type of the data directory. - * + * * @return true if the operation was successfully performed. False otherwise. */ public static boolean fixAppDataPermissions(Context context) { @@ -136,4 +138,14 @@ public class Util { dialog.dismiss(); } + + /** + * Format a path properly. + * + * @param path String containing the path that needs formatting. + * @return formatted file path as a string. + */ + public static String formatPath(String path) { + return new File(path).toURI().normalize().getPath(); + } } diff --git a/src/main/res/layout/activity_share.xml b/src/main/res/layout/activity_share.xml index 24f251c2..eb2df79e 100644 --- a/src/main/res/layout/activity_share.xml +++ b/src/main/res/layout/activity_share.xml @@ -1,19 +1,22 @@ - + android:layout_height="match_parent" + android:fillViewport="true" + android:orientation="vertical" + tools:context="com.nutomic.syncthingandroid.activities.ShareActivity"> - + android:layout_marginLeft="8dp" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintBottom_toTopOf="@+id/folders" + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/namesTitle" + app:layout_constraintVertical_bias="0.0" /> + android:layout_marginLeft="8dp" + android:layout_marginStart="8dp" + android:layout_marginTop="7dp" + android:text="@string/folder_title" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toBottomOf="@+id/name" /> + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/folder_title" />