From 30efd903b5cd38a06b363655d7a69c6249378063 Mon Sep 17 00:00:00 2001 From: Catfriend1 Date: Tue, 12 Feb 2019 00:24:26 +0100 Subject: [PATCH] Allow changing suggested path for new folder (fixes #309) (#318) * Add pref: Suggested new folder root (fixes #309) * Fix NPE in offline config reader (fixes #316) after folder has been created while Syncthing was not running * Fix language pref and root pref * Fix action bar not showing on sub-prefs screen (fixes #317) on Android < 7 * Add DATA, MEDIA to FileUtils#getExternalFilesDirUri (fixes #309) * SettingsActivity: Remove pref "SuggestNewFolderRoot" on API < 21 * Update Constants PREF_SUGGEST_NEW_FOLDER_ROOT_DATA, PREF_SUGGEST_NEW_FOLDER_ROOT_MEDIA * Update FolderActivity#onPathViewClick (fixes #309) to respect PREF_SUGGEST_NEW_FOLDER_ROOT_DATA * Imported de translation --- .../activities/FolderActivity.java | 10 ++- .../activities/SettingsActivity.java | 65 ++++++++++++------- .../syncthingandroid/service/Constants.java | 13 +++- .../syncthingandroid/util/FileUtils.java | 47 +++++++++++--- app/src/main/res/values-de/strings.xml | 7 ++ app/src/main/res/values/arrays.xml | 6 ++ app/src/main/res/values/strings.xml | 7 ++ app/src/main/res/xml/app_settings.xml | 8 +++ 8 files changed, 128 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/nutomic/syncthingandroid/activities/FolderActivity.java b/app/src/main/java/com/nutomic/syncthingandroid/activities/FolderActivity.java index 66dcb0eb..a1ff08ed 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/activities/FolderActivity.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/activities/FolderActivity.java @@ -41,6 +41,7 @@ import com.nutomic.syncthingandroid.service.SyncthingServiceBinder; import com.nutomic.syncthingandroid.SyncthingApp; import com.nutomic.syncthingandroid.util.ConfigRouter; import com.nutomic.syncthingandroid.util.FileUtils; +import com.nutomic.syncthingandroid.util.FileUtils.ExternalStorageDirType; import com.nutomic.syncthingandroid.util.TextWatcherAdapter; import com.nutomic.syncthingandroid.util.Util; @@ -293,13 +294,20 @@ public class FolderActivity extends SyncthingActivity { @SuppressLint("InlinedAPI") private void onPathViewClick() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // API < 21 startActivityForResult(FolderPickerActivity.createIntent(this, mFolder.path, null), FolderPickerActivity.DIRECTORY_REQUEST_CODE); return; } + String prefSuggestNewFolderRoot = mPreferences.getString(Constants.PREF_SUGGEST_NEW_FOLDER_ROOT, Constants.PREF_SUGGEST_NEW_FOLDER_ROOT_DATA); // This has to be android.net.Uri as it implements a Parcelable. - android.net.Uri externalFilesDirUri = FileUtils.getExternalFilesDirUri(FolderActivity.this); + android.net.Uri externalFilesDirUri = FileUtils.getExternalFilesDirUri( + FolderActivity.this, + prefSuggestNewFolderRoot.equals(Constants.PREF_SUGGEST_NEW_FOLDER_ROOT_DATA) ? + ExternalStorageDirType.DATA : + ExternalStorageDirType.MEDIA + ); // Display storage access framework directory picker UI. Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); diff --git a/app/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java b/app/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java index 2fb4030c..e3c0aec4 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java @@ -154,7 +154,6 @@ public class SettingsActivity extends SyncthingActivity { private Dialog mCurrentPrefScreenDialog = null; private Preference mCategoryRunConditions; - private CheckBoxPreference mStartServiceOnBoot; private ListPreference mPowerSource; private CheckBoxPreference mRunOnMobileData; private CheckBoxPreference mRunOnWifi; @@ -163,6 +162,13 @@ public class SettingsActivity extends SyncthingActivity { private WifiSsidPreference mWifiSsidWhitelist; private CheckBoxPreference mRunInFlightMode; + /* Behaviour */ + private CheckBoxPreference mStartServiceOnBoot; + private CheckBoxPreference mUseRoot; + private ListPreference mSuggestNewFolderRoot; + private Languages mLanguages; + + /* Syncthing Options */ private Preference mCategorySyncthingOptions; private EditTextPreference mDeviceName; private EditTextPreference mListenAddresses; @@ -179,7 +185,6 @@ public class SettingsActivity extends SyncthingActivity { private CheckBoxPreference mUrAccepted; /* Experimental options */ - private CheckBoxPreference mUseRoot; private CheckBoxPreference mUseWakelock; private CheckBoxPreference mUseTor; private EditTextPreference mSocksProxyAddress; @@ -248,14 +253,10 @@ public class SettingsActivity extends SyncthingActivity { if (Build.VERSION.SDK_INT >= 24) { categoryBehaviour.removePreference(languagePref); } else { - Languages languages = new Languages(getActivity()); - languagePref.setDefaultValue(Languages.USE_SYSTEM_DEFAULT); - languagePref.setEntries(languages.getAllNames()); - languagePref.setEntryValues(languages.getSupportedLocales()); - languagePref.setOnPreferenceChangeListener((p, o) -> { - languages.forceChangeLanguage(getActivity(), (String) o); - return false; - }); + mLanguages = new Languages(getActivity()); + languagePref.setDefaultValue(mLanguages.USE_SYSTEM_DEFAULT); + languagePref.setEntries(mLanguages.getAllNames()); + languagePref.setEntryValues(mLanguages.getSupportedLocales()); } PreferenceScreen screen = getPreferenceScreen(); @@ -294,6 +295,14 @@ public class SettingsActivity extends SyncthingActivity { (CheckBoxPreference) findPreference(Constants.PREF_START_SERVICE_ON_BOOT); mUseRoot = (CheckBoxPreference) findPreference(Constants.PREF_USE_ROOT); + mSuggestNewFolderRoot = + (ListPreference) findPreference(Constants.PREF_SUGGEST_NEW_FOLDER_ROOT); + screen.findPreference(Constants.PREF_SUGGEST_NEW_FOLDER_ROOT).setSummary(mSuggestNewFolderRoot.getEntry()); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // Remove preference as FileUtils#getExternalFilesDirUri is only supported on API 21+ (LOLLIPOP+). + categoryBehaviour.removePreference(mSuggestNewFolderRoot); + } + setPreferenceCategoryChangeListener(categoryBehaviour, this::onBehaviourPreferenceChange); /* Syncthing options */ mDeviceName = (EditTextPreference) findPreference("deviceName"); @@ -350,7 +359,6 @@ public class SettingsActivity extends SyncthingActivity { mUseWakelock.setChecked(false); } - mUseRoot.setOnPreferenceClickListener(this); mUseWakelock.setOnPreferenceChangeListener(this); mUseTor.setOnPreferenceChangeListener(this); @@ -557,6 +565,27 @@ public class SettingsActivity extends SyncthingActivity { return true; } + public boolean onBehaviourPreferenceChange(Preference preference, Object o) { + switch (preference.getKey()) { + case Constants.PREF_USE_ROOT: + if ((Boolean) o) { + new TestRootTask(this).execute(); + } else { + new Thread(() -> Util.fixAppDataPermissions(getActivity())).start(); + mPendingConfig = true; + } + break; + case Constants.PREF_SUGGEST_NEW_FOLDER_ROOT: + mSuggestNewFolderRoot.setValue(o.toString()); + preference.setSummary(mSuggestNewFolderRoot.getEntry()); + break; + case Languages.PREFERENCE_LANGUAGE: + mLanguages.forceChangeLanguage(getActivity(), (String) o); + return false; + } + return true; + } + public boolean onSyncthingPreferenceChange(Preference preference, Object o) { Splitter splitter = Splitter.on(",").trimResults().omitEmptyStrings(); switch (preference.getKey()) { @@ -710,16 +739,6 @@ public class SettingsActivity extends SyncthingActivity { public boolean onPreferenceClick(Preference preference) { final Intent intent; switch (preference.getKey()) { - case Constants.PREF_USE_ROOT: - if (mUseRoot.isChecked()) { - // Only check preference after root was granted. - mUseRoot.setChecked(false); - new TestRootTask(this).execute(); - } else { - new Thread(() -> Util.fixAppDataPermissions(getActivity())).start(); - mPendingConfig = true; - } - return true; case KEY_OPEN_ISSUE_TRACKER: intent = new Intent(getActivity(), WebViewActivity.class); intent.putExtra(WebViewActivity.EXTRA_WEB_URL, getString(R.string.issue_tracker_url)); @@ -829,13 +848,15 @@ public class SettingsActivity extends SyncthingActivity { if (settingsFragment == null) { return; } + settingsFragment.mUseRoot.setOnPreferenceChangeListener(null); + settingsFragment.mUseRoot.setChecked(haveRoot); if (haveRoot) { settingsFragment.mPendingConfig = true; - settingsFragment.mUseRoot.setChecked(true); } else { Toast.makeText(settingsFragment.getActivity(), R.string.toast_root_denied, Toast.LENGTH_SHORT) .show(); } + settingsFragment.mUseRoot.setOnPreferenceChangeListener(settingsFragment::onBehaviourPreferenceChange); } } diff --git a/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java b/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java index 1fcbc098..8d8eb10b 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java @@ -27,14 +27,23 @@ public class Constants { public static final String PREF_RUN_IN_FLIGHT_MODE = "run_in_flight_mode"; // Preferences - Behaviour - public static final String PREF_USE_ROOT = "use_root"; + public static final String PREF_USE_ROOT = "use_root"; + + public static final String PREF_SUGGEST_NEW_FOLDER_ROOT = "suggest_new_folder_root"; + public static final String PREF_SUGGEST_NEW_FOLDER_ROOT_DATA = "external_android_data"; + public static final String PREF_SUGGEST_NEW_FOLDER_ROOT_MEDIA = "external_android_media"; + + + // Preferences - Troubleshooting public static final String PREF_ENVIRONMENT_VARIABLES = "environment_variables"; public static final String PREF_DEBUG_FACILITIES_ENABLED = "debug_facilities_enabled"; - public static final String PREF_USE_WAKE_LOCK = "wakelock_while_binary_running"; + + // Preferences - Experimental public static final String PREF_USE_TOR = "use_tor"; public static final String PREF_SOCKS_PROXY_ADDRESS = "socks_proxy_address"; public static final String PREF_HTTP_PROXY_ADDRESS = "http_proxy_address"; public static final String PREF_BROADCAST_SERVICE_CONTROL = "broadcast_service_control"; + public static final String PREF_USE_WAKE_LOCK = "wakelock_while_binary_running"; // Preferences - per Folder and Device Sync Conditions public static final String PREF_OBJECT_PREFIX_FOLDER = "sc_folder_"; diff --git a/app/src/main/java/com/nutomic/syncthingandroid/util/FileUtils.java b/app/src/main/java/com/nutomic/syncthingandroid/util/FileUtils.java index 63f68b3e..f4d2613d 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/util/FileUtils.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/util/FileUtils.java @@ -47,6 +47,11 @@ public class FileUtils { private static final String PRIMARY_VOLUME_NAME = "primary"; private static final String HOME_VOLUME_NAME = "home"; + public enum ExternalStorageDirType { + DATA, + MEDIA + } + @Nullable @TargetApi(21) public static String getAbsolutePathFromSAFUri(Context context, @Nullable final Uri safResultUri) { @@ -155,24 +160,37 @@ public class FileUtils { * has been blocked since Android 7+, we need to build the Uri * manually after discovering the first external storage. * This is crucial to assist the user finding a writeable folder - * to use syncthing's two way sync feature. + * to use Syncthing's two way sync feature. + * API for getExternalFilesDirs(): 19+ (KITKAT+) + * API for getExternalMediaDirs(): 21+ (LOLLIPOP+) */ - @TargetApi(19) - public static android.net.Uri getExternalFilesDirUri(Context context) { + @TargetApi(21) + public static android.net.Uri getExternalFilesDirUri(Context context, ExternalStorageDirType extDirType) { try { /** * Determine the app's private data folder on external storage if present. * e.g. "/storage/abcd-efgh/Android/[PACKAGE_NAME]/files" */ ArrayList externalFilesDir = new ArrayList<>(); - externalFilesDir.addAll(Arrays.asList(context.getExternalFilesDirs(null))); - externalFilesDir.remove(context.getExternalFilesDir(null)); + switch(extDirType ){ + case DATA: + externalFilesDir.addAll(Arrays.asList(context.getExternalFilesDirs(null))); + externalFilesDir.remove(context.getExternalFilesDir(null)); + break; + case MEDIA: + externalFilesDir.addAll(Arrays.asList(context.getExternalMediaDirs())); + if (externalFilesDir.size() > 0) { + externalFilesDir.remove(externalFilesDir.get(0)); + } + break; + } externalFilesDir.remove(null); // getExternalFilesDirs may return null for an ejected SDcard. if (externalFilesDir.size() == 0) { Log.w(TAG, "Could not determine app's private files directory on external storage."); return null; } String absPath = externalFilesDir.get(0).getAbsolutePath(); + // Log.v(TAG, "getExternalFilesDirUri: absPath=" + absPath); String[] segments = absPath.split("/"); if (segments.length < 2) { Log.w(TAG, "Could not extract volumeId from app's private files path '" + absPath + "'"); @@ -180,11 +198,20 @@ public class FileUtils { } // Extract the volumeId, e.g. "abcd-efgh" String volumeId = segments[2]; - // Build the content Uri for our private "files" folder. - return android.net.Uri.parse( - "content://com.android.externalstorage.documents/document/" + - volumeId + "%3AAndroid%2Fdata%2F" + - context.getPackageName() + "%2Ffiles"); + switch(extDirType ){ + case DATA: + // Build the content Uri for our private ".../data/[PKG_NAME]/files" folder. + return android.net.Uri.parse( + "content://com.android.externalstorage.documents/document/" + + volumeId + "%3AAndroid%2Fdata%2F" + + context.getPackageName() + "%2Ffiles"); + case MEDIA: + // Build the content Uri for our private ".../media/[PKG_NAME]" folder. + return android.net.Uri.parse( + "content://com.android.externalstorage.documents/document/" + + volumeId + "%3AAndroid%2Fmedia%2F" + + context.getPackageName()); + } } catch (Exception e) { Log.w(TAG, "getExternalFilesDirUri exception", e); } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 9c5558c2..f7272a56 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -439,6 +439,13 @@ Bitte melden Sie auftretende Probleme via GitHub. Wenn Syncthing unter dem Root-Benutzer ausgeführt wird, hat es Schreibzugriff auf Ordner, die Android normalerweise auf schreibgeschützten Zugriff beschränkt. Verwende diese Funktion mit Bedacht. + Wähle Stamm für neue Ordner + + + [Ext_Speicher]/Android/data + [Ext_Speicher]/Android/media + + Sprache Anwendungssprache ändern diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index a61c2a44..de74ee89 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -15,4 +15,10 @@ battery_power + + + external_android_data + external_android_media + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a058e2c7..b221693c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -442,6 +442,13 @@ Please report any problems you encounter via Github. Running Syncthing as root allows it to write to folders Android normally restricts to be readonly accessed. Use this feature cautious. + Select new folder root + + + [external_storage]/Android/data + [external_storage]/Android/media + + Language Change the app language diff --git a/app/src/main/res/xml/app_settings.xml b/app/src/main/res/xml/app_settings.xml index 9a8b1928..dd90d262 100644 --- a/app/src/main/res/xml/app_settings.xml +++ b/app/src/main/res/xml/app_settings.xml @@ -90,6 +90,14 @@ android:summary="@string/use_root_summary" android:defaultValue="false" /> + +