diff --git a/app/src/main/java/com/nutomic/syncthingandroid/activities/MainActivity.java b/app/src/main/java/com/nutomic/syncthingandroid/activities/MainActivity.java index cf34c232..fe91b459 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/activities/MainActivity.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/activities/MainActivity.java @@ -257,7 +257,12 @@ public class MainActivity extends StateDialogActivity // SyncthingService needs to be started from this activity as the user // can directly launch this activity from the recent activity switcher. - startService(new Intent(this, SyncthingService.class)); + Intent serviceIntent = new Intent(this, SyncthingService.class); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(serviceIntent); + } else { + startService(serviceIntent); + } onNewIntent(getIntent()); } 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 47c7bf9f..90747317 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java @@ -87,7 +87,7 @@ public class SettingsActivity extends SyncthingActivity { public static class SettingsFragment extends PreferenceFragment implements SyncthingActivity.OnServiceConnectedListener, SyncthingService.OnServiceStateChangeListener, Preference.OnPreferenceChangeListener, - Preference.OnPreferenceClickListener, SharedPreferences.OnSharedPreferenceChangeListener { + Preference.OnPreferenceClickListener { private static final String TAG = "SettingsFragment"; private static final String KEY_EXPORT_CONFIG = "export_config"; @@ -100,7 +100,7 @@ public class SettingsActivity extends SyncthingActivity { @Inject SharedPreferences mPreferences; private Preference mCategoryRunConditions; - private CheckBoxPreference mAlwaysRunInBackground; + private CheckBoxPreference mStartServiceOnBoot; private ListPreference mPowerSource; private CheckBoxPreference mRunOnMobileData; private CheckBoxPreference mRunOnWifi; @@ -152,7 +152,6 @@ public class SettingsActivity extends SyncthingActivity { super.onCreate(savedInstanceState); ((SyncthingApp) getActivity().getApplication()).component().inject(this); ((SyncthingActivity) getActivity()).registerOnServiceConnectedListener(this); - mPreferences.registerOnSharedPreferenceChangeListener(this); } /** @@ -166,8 +165,8 @@ public class SettingsActivity extends SyncthingActivity { addPreferencesFromResource(R.xml.app_settings); PreferenceScreen screen = getPreferenceScreen(); - mAlwaysRunInBackground = - (CheckBoxPreference) findPreference(Constants.PREF_ALWAYS_RUN_IN_BACKGROUND); + mStartServiceOnBoot = + (CheckBoxPreference) findPreference(Constants.PREF_START_SERVICE_ON_BOOT); mPowerSource = (ListPreference) findPreference(Constants.PREF_POWER_SOURCE); mRunOnMobileData = @@ -196,10 +195,6 @@ public class SettingsActivity extends SyncthingActivity { }); } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - categoryBehaviour.removePreference(findPreference(Constants.PREF_NOTIFICATION_TYPE)); - } - mDeviceName = (EditTextPreference) findPreference("deviceName"); mListenAddresses = (EditTextPreference) findPreference("listenAddresses"); mMaxRecvKbps = (EditTextPreference) findPreference("maxRecvKbps"); @@ -346,7 +341,6 @@ public class SettingsActivity extends SyncthingActivity { @Override public void onDestroy() { - mPreferences.unregisterOnSharedPreferenceChangeListener(this); if (mSyncthingService != null) { mSyncthingService.unregisterOnServiceStateChangeListener(this); } @@ -455,15 +449,16 @@ public class SettingsActivity extends SyncthingActivity { @Override public void onStop() { - if (mPendingConfig) { - if (mSyncthingService != null && mApi != null && - mSyncthingService.getCurrentState() != SyncthingService.State.DISABLED) { - mApi.saveConfigAndRestart(); - mPendingConfig = false; + if (mSyncthingService != null) { + mNotificationHandler.updatePersistentNotification(mSyncthingService); + if (mPendingConfig) { + if (mApi != null && + mSyncthingService.getCurrentState() != SyncthingService.State.DISABLED) { + mApi.saveConfigAndRestart(); + mPendingConfig = false; + } } - } - if (mPendingRunConditions) { - if (mSyncthingService != null) { + if (mPendingRunConditions) { mSyncthingService.evaluateRunConditions(); } } @@ -620,21 +615,6 @@ public class SettingsActivity extends SyncthingActivity { } } - /** - * Update notification after that preference changes. We can't use onPreferenceChange() as - * the preference value isn't persisted there, and the NotificationHandler accesses the - * preference directly. - * - * This function is called when the activity is opened, so we need to make sure the service - * is connected. - */ - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (key.equals(Constants.PREF_NOTIFICATION_TYPE) && mSyncthingService != null) { - mNotificationHandler.updatePersistentNotification(mSyncthingService); - } - } - /** * Enables or disables {@link #mUseRoot} preference depending whether root is available. */ diff --git a/app/src/main/java/com/nutomic/syncthingandroid/activities/WebGuiActivity.java b/app/src/main/java/com/nutomic/syncthingandroid/activities/WebGuiActivity.java index 04688ce0..9e21780c 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/activities/WebGuiActivity.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/activities/WebGuiActivity.java @@ -145,7 +145,12 @@ public class WebGuiActivity extends StateDialogActivity // SyncthingService needs to be started from this activity as the user // can directly launch this activity from the recent activity switcher. - startService(new Intent(this, SyncthingService.class)); + Intent serviceIntent = new Intent(this, SyncthingService.class); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(serviceIntent); + } else { + startService(serviceIntent); + } } @Override diff --git a/app/src/main/java/com/nutomic/syncthingandroid/fragments/DrawerFragment.java b/app/src/main/java/com/nutomic/syncthingandroid/fragments/DrawerFragment.java index 493a9320..15fa8dbb 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/fragments/DrawerFragment.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/fragments/DrawerFragment.java @@ -1,5 +1,6 @@ package com.nutomic.syncthingandroid.fragments; +import android.app.AlertDialog; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; @@ -53,6 +54,7 @@ public class DrawerFragment extends Fragment implements View.OnClickListener { private Timer mTimer; private MainActivity mActivity; + private SharedPreferences sharedPreferences = null; public void onDrawerOpened() { mTimer = new Timer(); @@ -94,6 +96,9 @@ public class DrawerFragment extends Fragment implements View.OnClickListener { @Override public void onViewCreated(View view, Bundle savedInstanceState) { + mActivity = (MainActivity) getActivity(); + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mActivity); + mCpuUsage = view.findViewById(R.id.cpu_usage); mRamUsage = view.findViewById(R.id.ram_usage); mDownload = view.findViewById(R.id.download); @@ -246,8 +251,23 @@ public class DrawerFragment extends Fragment implements View.OnClickListener { mActivity.closeDrawer(); break; case R.id.drawerActionExit: - mActivity.stopService(new Intent(mActivity, SyncthingService.class)); - mActivity.finish(); + if (sharedPreferences != null && sharedPreferences.getBoolean(Constants.PREF_START_SERVICE_ON_BOOT, false)) { + /** + * App is running as a service. Show an explanation why exiting syncthing is an + * extraordinary request, then ask the user to confirm. + */ + AlertDialog mExitConfirmationDialog = new AlertDialog.Builder(mActivity) + .setTitle(R.string.dialog_exit_while_running_as_service_title) + .setMessage(R.string.dialog_exit_while_running_as_service_message) + .setPositiveButton(R.string.yes, (d, i) -> { + doExit(); + }) + .setNegativeButton(R.string.no, (d, i) -> {}) + .show(); + } else { + // App is not running as a service. + doExit(); + } mActivity.closeDrawer(); break; case R.id.drawerActionShowQrCode: @@ -258,6 +278,15 @@ public class DrawerFragment extends Fragment implements View.OnClickListener { private boolean alwaysRunInBackground() { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity()); - return sp.getBoolean(Constants.PREF_ALWAYS_RUN_IN_BACKGROUND, false); + return sp.getBoolean(Constants.PREF_START_SERVICE_ON_BOOT, false); + } + + private void doExit() { + if (mActivity == null || mActivity.isFinishing()) { + return; + } + Log.i(TAG, "Exiting app on user request"); + mActivity.stopService(new Intent(mActivity, SyncthingService.class)); + mActivity.finish(); } } diff --git a/app/src/main/java/com/nutomic/syncthingandroid/receiver/AppConfigReceiver.java b/app/src/main/java/com/nutomic/syncthingandroid/receiver/AppConfigReceiver.java index ccfcd26a..3e2265e5 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/receiver/AppConfigReceiver.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/receiver/AppConfigReceiver.java @@ -25,7 +25,7 @@ public class AppConfigReceiver extends BroadcastReceiver { /** * Stop the Syncthing-Service - * If alwaysRunInBackground is enabled the service must not be stopped. Instead a + * If startServiceOnBoot is enabled the service must not be stopped. Instead a * notification is presented to the user. */ private static final String ACTION_STOP = "com.nutomic.syncthingandroid.action.STOP"; @@ -40,7 +40,7 @@ public class AppConfigReceiver extends BroadcastReceiver { BootReceiver.startServiceCompat(context); break; case ACTION_STOP: - if (alwaysRunInBackground(context)) { + if (startServiceOnBoot(context)) { mNotificationHandler.showStopSyncthingWarningNotification(); } else { context.stopService(new Intent(context, SyncthingService.class)); @@ -49,8 +49,8 @@ public class AppConfigReceiver extends BroadcastReceiver { } } - private static boolean alwaysRunInBackground(Context context) { + private static boolean startServiceOnBoot(Context context) { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - return sp.getBoolean(Constants.PREF_ALWAYS_RUN_IN_BACKGROUND, false); + return sp.getBoolean(Constants.PREF_START_SERVICE_ON_BOOT, false); } } diff --git a/app/src/main/java/com/nutomic/syncthingandroid/receiver/BootReceiver.java b/app/src/main/java/com/nutomic/syncthingandroid/receiver/BootReceiver.java index 67b835a8..1b0d7e50 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/receiver/BootReceiver.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/receiver/BootReceiver.java @@ -18,14 +18,14 @@ public class BootReceiver extends BroadcastReceiver { !intent.getAction().equals(Intent.ACTION_MY_PACKAGE_REPLACED)) return; - if (!alwaysRunInBackground(context)) + if (!startServiceOnBoot(context)) return; startServiceCompat(context); } /** - * Workaround for starting service from background on Android 8. + * Workaround for starting service from background on Android 8+. * * https://stackoverflow.com/a/44505719/1837158 */ @@ -39,8 +39,8 @@ public class BootReceiver extends BroadcastReceiver { } } - private static boolean alwaysRunInBackground(Context context) { + private static boolean startServiceOnBoot(Context context) { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - return sp.getBoolean(Constants.PREF_ALWAYS_RUN_IN_BACKGROUND, false); + return sp.getBoolean(Constants.PREF_START_SERVICE_ON_BOOT, false); } } 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 53d54c83..dfde0607 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/service/Constants.java @@ -11,7 +11,7 @@ public class Constants { public static final String FILENAME_SYNCTHING_BINARY = "libsyncthing.so"; // Preferences - Run conditions - public static final String PREF_ALWAYS_RUN_IN_BACKGROUND = "always_run_in_background"; + public static final String PREF_START_SERVICE_ON_BOOT = "always_run_in_background"; public static final String PREF_RUN_ON_MOBILE_DATA = "run_on_mobile_data"; public static final String PREF_RUN_ON_WIFI = "run_on_wifi"; public static final String PREF_RUN_ON_METERED_WIFI = "run_on_metered_wifi"; @@ -25,7 +25,6 @@ public class Constants { public static final String PREF_FIRST_START = "first_start"; public static final String PREF_START_INTO_WEB_GUI = "start_into_web_gui"; public static final String PREF_USE_ROOT = "use_root"; - public static final String PREF_NOTIFICATION_TYPE = "notification_type"; 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"; diff --git a/app/src/main/java/com/nutomic/syncthingandroid/service/NotificationHandler.java b/app/src/main/java/com/nutomic/syncthingandroid/service/NotificationHandler.java index 88e98258..1298a091 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/service/NotificationHandler.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/service/NotificationHandler.java @@ -8,7 +8,6 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Build; -import android.preference.PreferenceManager; import android.support.annotation.StringRes; import android.support.v4.app.NotificationCompat; import android.util.Log; @@ -43,6 +42,9 @@ public class NotificationHandler { private final NotificationChannel mPersistentChannelWaiting; private final NotificationChannel mInfoChannel; + private Boolean lastStartForegroundService = false; + private Boolean appShutdownInProgress = false; + public NotificationHandler(Context context) { ((SyncthingApp) context.getApplicationContext()).component().inject(this); mContext = context; @@ -88,84 +90,102 @@ public class NotificationHandler { } /** - * Shows or hides the persistent notification based on running state and - * {@link Constants#PREF_NOTIFICATION_TYPE}. + * Shows, updates or hides the notification. */ public void updatePersistentNotification(SyncthingService service) { - String type = mPreferences.getString(Constants.PREF_NOTIFICATION_TYPE, "low_priority"); - - // Always use startForeground() if app is set to always run. This makes sure the app - // is not killed, and we don't miss wifi/charging events. - // On Android 8, this behaviour is mandatory to receive broadcasts. - // https://stackoverflow.com/a/44505719/1837158 - boolean foreground = mPreferences.getBoolean(Constants.PREF_ALWAYS_RUN_IN_BACKGROUND, false); - - // Foreground priority requires a notification so this ensures that we either have a - // "default" or "low_priority" notification, but not "none". - if ("none".equals(type) && foreground) { - type = "low_priority"; - } - + boolean startServiceOnBoot = mPreferences.getBoolean(Constants.PREF_START_SERVICE_ON_BOOT, false); State currentServiceState = service.getCurrentState(); boolean syncthingRunning = currentServiceState == SyncthingService.State.ACTIVE || currentServiceState == SyncthingService.State.STARTING; - if (foreground || (syncthingRunning && !type.equals("none"))) { - int title = R.string.syncthing_terminated; - switch (currentServiceState) { - case ERROR: - case INIT: - break; - case DISABLED: - title = R.string.syncthing_disabled; - break; - case STARTING: - case ACTIVE: - title = R.string.syncthing_active; - break; - default: - break; + boolean startForegroundService = false; + if (!appShutdownInProgress) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + /** + * Android 7 and lower: + * The app may run in background and monitor run conditions even if it is not + * running as a foreground service. For that reason, we can use a normal + * notification if syncthing is DISABLED. + */ + startForegroundService = startServiceOnBoot || syncthingRunning; + } else { + /** + * Android 8+: + * Always use startForeground. + * This makes sure the app is not killed, and we don't miss run condition events. + * On Android 8+, this behaviour is mandatory to receive broadcasts. + * https://stackoverflow.com/a/44505719/1837158 + * Foreground priority requires a notification so this ensures that we either have a + * "default" or "low_priority" notification, but not "none". + */ + startForegroundService = true; } + } - /** - * We no longer need to launch FirstStartActivity instead of MainActivity as - * {@link SyncthingService#onStartCommand} will check for denied permissions. - */ - Intent intent = new Intent(mContext, MainActivity.class); + // Check if we have to stopForeground. + if (startForegroundService != lastStartForegroundService) { + if (!startForegroundService) { + Log.v(TAG, "Stopping foreground service"); + service.stopForeground(false); + } + } - // Reason for two separate IDs: if one of the notification channels is hidden then - // the startForeground() below won't update the notification but use the old one - int idToShow = syncthingRunning ? ID_PERSISTENT : ID_PERSISTENT_WAITING; - int idToCancel = syncthingRunning ? ID_PERSISTENT_WAITING : ID_PERSISTENT; - NotificationChannel channel = syncthingRunning ? mPersistentChannel : mPersistentChannelWaiting; - NotificationCompat.Builder builder = getNotificationBuilder(channel) - .setContentTitle(mContext.getString(title)) - .setSmallIcon(R.drawable.ic_stat_notify) - .setOngoing(true) - .setOnlyAlertOnce(true) - .setContentIntent(PendingIntent.getActivity(mContext, 0, intent, 0)); - if (type.equals("low_priority")) - builder.setPriority(NotificationCompat.PRIORITY_MIN); + // Prepare notification builder. + int title = R.string.syncthing_terminated; + switch (currentServiceState) { + case ERROR: + case INIT: + break; + case DISABLED: + title = R.string.syncthing_disabled; + break; + case STARTING: + title = R.string.syncthing_starting; + break; + case ACTIVE: + title = R.string.syncthing_active; + break; + default: + break; + } - if (foreground) { + /** + * Reason for two separate IDs: if one of the notification channels is hidden then + * the startForeground() below won't update the notification but use the old one. + */ + int idToShow = syncthingRunning ? ID_PERSISTENT : ID_PERSISTENT_WAITING; + int idToCancel = syncthingRunning ? ID_PERSISTENT_WAITING : ID_PERSISTENT; + Intent intent = new Intent(mContext, MainActivity.class); + NotificationChannel channel = syncthingRunning ? mPersistentChannel : mPersistentChannelWaiting; + NotificationCompat.Builder builder = getNotificationBuilder(channel) + .setContentTitle(mContext.getString(title)) + .setSmallIcon(R.drawable.ic_stat_notify) + .setOngoing(true) + .setOnlyAlertOnce(true) + .setPriority(NotificationCompat.PRIORITY_MIN) + .setContentIntent(PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); + if (!appShutdownInProgress) { + if (startForegroundService) { + Log.v(TAG, "Starting foreground service or updating notification"); service.startForeground(idToShow, builder.build()); } else { - service.stopForeground(false); // ensure no longer running with foreground priority + Log.v(TAG, "Updating notification"); mNotificationManager.notify(idToShow, builder.build()); } - mNotificationManager.cancel(idToCancel); } else { - // ensure no longer running with foreground priority - cancelPersistentNotification(service); + mNotificationManager.cancel(idToShow); } + mNotificationManager.cancel(idToCancel); + + // Remember last notification visibility. + lastStartForegroundService = startForegroundService; } - public void cancelPersistentNotification(SyncthingService service) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && alwaysRunInBackground()) - return; - - service.stopForeground(false); - mNotificationManager.cancel(ID_PERSISTENT); - mNotificationManager.cancel(ID_PERSISTENT_WAITING); + /** + * Called by {@link SyncthingService#onStart} {@link SyncthingService#onDestroy} + * to indicate app startup and shutdown. + */ + public void setAppShutdownInProgress(Boolean newValue) { + appShutdownInProgress = newValue; } public void showCrashedNotification(@StringRes int title, boolean force) { @@ -280,9 +300,4 @@ public class NotificationHandler { } mNotificationManager.notify(ID_STOP_BACKGROUND_WARNING, nb.build()); } - - private boolean alwaysRunInBackground() { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); - return sp.getBoolean(Constants.PREF_ALWAYS_RUN_IN_BACKGROUND, false); - } } diff --git a/app/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java b/app/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java index e7928c6b..99fefa3b 100644 --- a/app/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java +++ b/app/src/main/java/com/nutomic/syncthingandroid/service/SyncthingService.java @@ -179,8 +179,12 @@ public class SyncthingService extends Service { * We need to recheck if we still have the storage permission. */ mStoragePermissionGranted = (ContextCompat.checkSelfPermission(this, - Manifest.permission.WRITE_EXTERNAL_STORAGE) == - PackageManager.PERMISSION_GRANTED); + Manifest.permission.WRITE_EXTERNAL_STORAGE) == + PackageManager.PERMISSION_GRANTED); + + if (mNotificationHandler != null) { + mNotificationHandler.setAppShutdownInProgress(false); + } } /** @@ -449,6 +453,9 @@ public class SyncthingService extends Service { */ mRunConditionMonitor.shutdown(); } + if (mNotificationHandler != null) { + mNotificationHandler.setAppShutdownInProgress(true); + } if (mStoragePermissionGranted) { synchronized (mStateLock) { if (mCurrentState == State.STARTING) { @@ -494,10 +501,6 @@ public class SyncthingService extends Service { mApi = null; } - if (mNotificationHandler != null) { - mNotificationHandler.cancelPersistentNotification(this); - } - if (mSyncthingRunnable != null) { mSyncthingRunnable.killSyncthing(); if (mSyncthingRunnableThread != null) { @@ -556,7 +559,7 @@ public class SyncthingService extends Service { } /** - * Called to notifiy listeners of an API change. + * Called to notify listeners of an API change. */ private void onServiceStateChange(State newState) { Log.v(TAG, "onServiceStateChange: from " + mCurrentState + " to " + newState); diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index feb79c9a..a61c2a44 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -15,10 +15,4 @@ battery_power - - normal - low_priority - none - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 92f6b118..1e65c80a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -43,6 +43,10 @@ Please report any problems you encounter via Github. + + Confirm to quit app + For your consideration: You configured the app to start automatically on boot. Therefore it monitors run conditions and syncs at any time in the background when conditions match. You should only quit manually if you run into severe problems. Otherwise, disable \'Start automatically on boot \' in the settings. Would you like to quit now until the device rebooted? + Add Folder @@ -135,6 +139,13 @@ Please report any problems you encounter via Github. Upload + + Status + Syncthing is starting. + Syncthing is running. + Syncthing is not running. + Syncthing has crashed. + diff --git a/app/src/main/res/xml/app_settings.xml b/app/src/main/res/xml/app_settings.xml index b444644e..852cb214 100644 --- a/app/src/main/res/xml/app_settings.xml +++ b/app/src/main/res/xml/app_settings.xml @@ -73,14 +73,6 @@ android:summary="@string/advanced_folder_picker_summary" android:defaultValue="false" /> - -