Răsfoiți Sursa

Make the name collision policy for auto uploads selectable, fixes #5599.

Signed-off-by: Joachim Meyer <joachim@joameyer.de>
Joachim Meyer 5 ani în urmă
părinte
comite
32da47d6a3

+ 26 - 13
src/main/java/com/owncloud/android/datamodel/SyncedFolder.java

@@ -40,6 +40,7 @@ public class SyncedFolder implements Serializable, Cloneable {
     private boolean subfolderByDate;
     private String account;
     private int uploadAction;
+    private int nameCollisionPolicy;
     private boolean enabled;
     private long enabledTimestampMs;
     private MediaFolderType type;
@@ -48,18 +49,19 @@ public class SyncedFolder implements Serializable, Cloneable {
     /**
      * constructor for new, to be persisted entity.
      *
-     * @param localPath       local path
-     * @param remotePath      remote path
-     * @param wifiOnly        upload on wifi only flag
-     * @param chargingOnly    upload on charging only
-     * @param existing        upload existing files
-     * @param subfolderByDate create sub-folders by date (month)
-     * @param account         the account owning the synced folder
-     * @param uploadAction    the action to be done after the upload
-     * @param enabled         flag if synced folder config is active
-     * @param timestampMs     the current timestamp in milliseconds
-     * @param type            the type of the folder
-     * @param hidden          hide item flag
+     * @param localPath           local path
+     * @param remotePath          remote path
+     * @param wifiOnly            upload on wifi only flag
+     * @param chargingOnly        upload on charging only
+     * @param existing            upload existing files
+     * @param subfolderByDate     create sub-folders by date (month)
+     * @param account             the account owning the synced folder
+     * @param uploadAction        the action to be done after the upload
+     * @param nameCollisionPolicy the behaviour to follow if detecting a collision
+     * @param enabled             flag if synced folder config is active
+     * @param timestampMs         the current timestamp in milliseconds
+     * @param type                the type of the folder
+     * @param hidden              hide item flag
      */
     public SyncedFolder(String localPath,
                         String remotePath,
@@ -69,12 +71,13 @@ public class SyncedFolder implements Serializable, Cloneable {
                         boolean subfolderByDate,
                         String account,
                         int uploadAction,
+                        int nameCollisionPolicy,
                         boolean enabled,
                         long timestampMs,
                         MediaFolderType type,
                         boolean hidden) {
         this(UNPERSISTED_ID, localPath, remotePath, wifiOnly, chargingOnly, existing, subfolderByDate, account,
-             uploadAction, enabled, timestampMs, type, hidden);
+             uploadAction, nameCollisionPolicy, enabled, timestampMs, type, hidden);
     }
 
     /**
@@ -91,6 +94,7 @@ public class SyncedFolder implements Serializable, Cloneable {
                            boolean subfolderByDate,
                            String account,
                            int uploadAction,
+                           int nameCollisionPolicy,
                            boolean enabled,
                            long timestampMs,
                            MediaFolderType type,
@@ -104,6 +108,7 @@ public class SyncedFolder implements Serializable, Cloneable {
         this.subfolderByDate = subfolderByDate;
         this.account = account;
         this.uploadAction = uploadAction;
+        this.nameCollisionPolicy = nameCollisionPolicy;
         this.setEnabled(enabled, timestampMs);
         this.type = type;
         this.hidden = hidden;
@@ -161,6 +166,10 @@ public class SyncedFolder implements Serializable, Cloneable {
         return this.uploadAction;
     }
 
+    public int getNameCollisionPolicy() {
+        return this.nameCollisionPolicy;
+    }
+
     public boolean isEnabled() {
         return this.enabled;
     }
@@ -213,6 +222,10 @@ public class SyncedFolder implements Serializable, Cloneable {
         this.uploadAction = uploadAction;
     }
 
+    public void setNameCollisionPolicy(int nameCollisionPolicy) {
+        this.nameCollisionPolicy = nameCollisionPolicy;
+    }
+
     public void setType(MediaFolderType type) {
         this.type = type;
     }

+ 4 - 2
src/main/java/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java

@@ -60,6 +60,7 @@ public class SyncedFolderDisplayItem extends SyncedFolder {
                                    boolean subfolderByDate,
                                    String account,
                                    int uploadAction,
+                                   int nameCollisionPolicy,
                                    boolean enabled,
                                    long timestampMs,
                                    List<String> filePaths,
@@ -68,7 +69,7 @@ public class SyncedFolderDisplayItem extends SyncedFolder {
                                    MediaFolderType type,
                                    boolean hidden) {
         super(id, localPath, remotePath, wifiOnly, chargingOnly, existing, subfolderByDate, account, uploadAction,
-              enabled, timestampMs, type, hidden);
+              nameCollisionPolicy, enabled, timestampMs, type, hidden);
         this.filePaths = filePaths;
         this.folderName = folderName;
         this.numberOfFiles = numberOfFiles;
@@ -83,11 +84,12 @@ public class SyncedFolderDisplayItem extends SyncedFolder {
                                    boolean subfolderByDate,
                                    String account,
                                    int uploadAction,
+                                   int nameCollisionPolicy,
                                    boolean enabled,
                                    long timestampMs,
                                    String folderName, MediaFolderType type, boolean hidden) {
         super(id, localPath, remotePath, wifiOnly, chargingOnly, existing, subfolderByDate, account, uploadAction,
-              enabled, timestampMs, type, hidden);
+              nameCollisionPolicy, enabled, timestampMs, type, hidden);
         this.folderName = folderName;
     }
 

+ 6 - 2
src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java

@@ -350,6 +350,8 @@ public class SyncedFolderProvider extends Observable {
                     ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT));
             int uploadAction = cursor.getInt(cursor.getColumnIndex(
                     ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION));
+            int nameCollisionPolicy = cursor.getInt(cursor.getColumnIndex(
+                ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_NAME_COLLISION_POLICY));
             boolean enabled = cursor.getInt(cursor.getColumnIndex(
                     ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1;
             long enabledTimestampMs = cursor.getLong(cursor.getColumnIndex(
@@ -360,8 +362,8 @@ public class SyncedFolderProvider extends Observable {
                 ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_HIDDEN)) == 1;
 
             syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, existing,
-                                            subfolderByDate, accountName, uploadAction, enabled, enabledTimestampMs,
-                                            type, hidden);
+                                            subfolderByDate, accountName, uploadAction, nameCollisionPolicy, enabled,
+                                            enabledTimestampMs, type, hidden);
         }
         return syncedFolder;
     }
@@ -385,6 +387,8 @@ public class SyncedFolderProvider extends Observable {
         cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE, syncedFolder.isSubfolderByDate());
         cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT, syncedFolder.getAccount());
         cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION, syncedFolder.getUploadAction());
+        cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_NAME_COLLISION_POLICY,
+               syncedFolder.getNameCollisionPolicy());
         cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE, syncedFolder.getType().getId());
         cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_HIDDEN, syncedFolder.isHidden());
 

+ 2 - 1
src/main/java/com/owncloud/android/db/ProviderMeta.java

@@ -31,7 +31,7 @@ import com.owncloud.android.MainApp;
  */
 public class ProviderMeta {
     public static final String DB_NAME = "filelist";
-    public static final int DB_VERSION = 54;
+    public static final int DB_VERSION = 55;
 
     private ProviderMeta() {
         // No instance
@@ -230,6 +230,7 @@ public class ProviderMeta {
         public static final String SYNCED_FOLDER_SUBFOLDER_BY_DATE = "subfolder_by_date";
         public static final String SYNCED_FOLDER_ACCOUNT = "account";
         public static final String SYNCED_FOLDER_UPLOAD_ACTION = "upload_option";
+        public static final String SYNCED_FOLDER_NAME_COLLISION_POLICY = "name_collision_policy";
         public static final String SYNCED_FOLDER_HIDDEN = "hidden";
 
         // Columns of external links table

+ 12 - 8
src/main/java/com/owncloud/android/jobs/FilesSyncJob.java

@@ -165,13 +165,6 @@ public class FilesSyncJob extends Job {
         SimpleDateFormat sFormatter,
         SyncedFolder syncedFolder
     ) {
-        String remotePath;
-        boolean subfolderByDate;
-        Integer uploadAction;
-        boolean needsCharging;
-        boolean needsWifi;
-        File file;
-        ArbitraryDataProvider arbitraryDataProvider;
         String accountName = syncedFolder.getAccount();
         Optional<User> optionalUser = userAccountManager.getUser(accountName);
         if (!optionalUser.isPresent()) {
@@ -179,12 +172,20 @@ public class FilesSyncJob extends Job {
         }
         User user = optionalUser.get();
 
+        ArbitraryDataProvider arbitraryDataProvider;
         if (lightVersion) {
             arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver());
         } else {
             arbitraryDataProvider = null;
         }
 
+        String remotePath;
+        boolean subfolderByDate;
+        Integer uploadAction;
+        FileUploader.NameCollisionPolicy nameCollisionPolicy;
+        boolean needsCharging;
+        boolean needsWifi;
+        File file;
         for (String path : filesystemDataProvider.getFilesForUpload(syncedFolder.getLocalPath(),
                 Long.toString(syncedFolder.getId()))) {
             file = new File(path);
@@ -197,12 +198,15 @@ public class FilesSyncJob extends Job {
                                                                   SettingsActivity.SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI);
                 String uploadActionString = resources.getString(R.string.syncedFolder_light_upload_behaviour);
                 uploadAction = getUploadAction(uploadActionString);
+                nameCollisionPolicy = FileUploader.NameCollisionPolicy.ASK_USER;
                 subfolderByDate = resources.getBoolean(R.bool.syncedFolder_light_use_subfolders);
                 remotePath = resources.getString(R.string.syncedFolder_remote_folder);
             } else {
                 needsCharging = syncedFolder.isChargingOnly();
                 needsWifi = syncedFolder.isWifiOnly();
                 uploadAction = syncedFolder.getUploadAction();
+                nameCollisionPolicy = FileUploader.NameCollisionPolicy.deserialize(
+                        syncedFolder.getNameCollisionPolicy());
                 subfolderByDate = syncedFolder.isSubfolderByDate();
                 remotePath = syncedFolder.getRemotePath();
             }
@@ -224,7 +228,7 @@ public class FilesSyncJob extends Job {
                 UploadFileOperation.CREATED_AS_INSTANT_PICTURE,
                 needsWifi,
                 needsCharging,
-                FileUploader.NameCollisionPolicy.ASK_USER
+                nameCollisionPolicy
             );
 
             filesystemDataProvider.updateFilesystemFileAsSentForUpload(path,

+ 26 - 1
src/main/java/com/owncloud/android/providers/FileContentProvider.java

@@ -49,6 +49,7 @@ import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.SyncedFolder;
 import com.owncloud.android.db.ProviderMeta;
 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
+import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.accounts.AccountUtils;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.shares.ShareType;
@@ -832,10 +833,11 @@ public class FileContentProvider extends ContentProvider {
                        + ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY + " INTEGER, "     // charging only
                        + ProviderTableMeta.SYNCED_FOLDER_EXISTING + " INTEGER, "          // existing
                        + ProviderTableMeta.SYNCED_FOLDER_ENABLED + " INTEGER, "           // enabled
-                       + ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS + " INTEGER, "           // enable date
+                       + ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS + " INTEGER, " // enable date
                        + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + " INTEGER, " // subfolder by date
                        + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + "  TEXT, "             // account
                        + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION + " INTEGER, "     // upload action
+                       + ProviderTableMeta.SYNCED_FOLDER_NAME_COLLISION_POLICY + " INTEGER, " // name collision policy
                        + ProviderTableMeta.SYNCED_FOLDER_TYPE + " INTEGER, "              // type
                        + ProviderTableMeta.SYNCED_FOLDER_HIDDEN + " INTEGER );"           // hidden
         );
@@ -2168,6 +2170,29 @@ public class FileContentProvider extends ContentProvider {
             if (!upgraded) {
                 Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
             }
+
+            if (oldVersion < 55 && newVersion >= 55) {
+                Log_OC.i(SQL, "Entering in the #55 add synced.name_collision_policy.");
+                db.beginTransaction();
+                try {
+                    // Add synced.name_collision_policy
+                    db.execSQL(ALTER_TABLE + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME +
+                                   ADD_COLUMN + ProviderTableMeta.SYNCED_FOLDER_NAME_COLLISION_POLICY + " INTEGER "); // integer
+
+                    // make sure all existing folders set to FileUploader.NameCollisionPolicy.ASK_USER.
+                    db.execSQL("UPDATE " + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME + " SET " +
+                                   ProviderTableMeta.SYNCED_FOLDER_NAME_COLLISION_POLICY + " = " +
+                                   FileUploader.NameCollisionPolicy.ASK_USER.serialize());
+                    upgraded = true;
+                    db.setTransactionSuccessful();
+                } finally {
+                    db.endTransaction();
+                }
+            }
+
+            if (!upgraded) {
+                Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
+            }
         }
     }
 }

+ 12 - 4
src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java

@@ -420,6 +420,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
                 syncedFolder.isSubfolderByDate(),
                 syncedFolder.getAccount(),
                 syncedFolder.getUploadAction(),
+                syncedFolder.getNameCollisionPolicy(),
                 syncedFolder.isEnabled(),
                 clock.getCurrentTime(),
                 filePaths,
@@ -448,6 +449,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
                 syncedFolder.isSubfolderByDate(),
                 syncedFolder.getAccount(),
                 syncedFolder.getUploadAction(),
+                syncedFolder.getNameCollisionPolicy(),
                 syncedFolder.isEnabled(),
                 clock.getCurrentTime(),
                 mediaFolder.filePaths,
@@ -475,6 +477,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
                 false,
                 getAccount().name,
                 FileUploader.LOCAL_BEHAVIOUR_FORGET,
+                FileUploader.NameCollisionPolicy.ASK_USER.serialize(),
                 false,
                 clock.getCurrentTime(),
                 mediaFolder.filePaths,
@@ -581,7 +584,8 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
                 Log.d(TAG, "Show custom folder dialog");
                 SyncedFolderDisplayItem emptyCustomFolder = new SyncedFolderDisplayItem(
                     SyncedFolder.UNPERSISTED_ID, null, null, true, false, true,
-                    false, getAccount().name, FileUploader.LOCAL_BEHAVIOUR_FORGET, false,
+                    false, getAccount().name, FileUploader.LOCAL_BEHAVIOUR_FORGET,
+                    FileUploader.NameCollisionPolicy.ASK_USER.serialize(), false,
                     clock.getCurrentTime(), null, MediaFolderType.CUSTOM, false);
                 onSyncFolderSettingsClick(0, emptyCustomFolder);
             }
@@ -714,8 +718,9 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
                     SyncedFolder.UNPERSISTED_ID, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(),
                     syncedFolder.isWifiOnly(), syncedFolder.isChargingOnly(),
                     syncedFolder.isExisting(), syncedFolder.isSubfolderByDate(), syncedFolder.getAccount(),
-                    syncedFolder.getUploadAction(), syncedFolder.isEnabled(), clock.getCurrentTime(),
-                    new File(syncedFolder.getLocalPath()).getName(), syncedFolder.getType(), syncedFolder.isHidden());
+                    syncedFolder.getUploadAction(), syncedFolder.getNameCollisionPolicy().serialize(),
+                    syncedFolder.isEnabled(), clock.getCurrentTime(), new File(syncedFolder.getLocalPath()).getName(),
+                    syncedFolder.getType(), syncedFolder.isHidden());
 
             saveOrUpdateSyncedFolder(newCustomFolder);
             adapter.addSyncFolderItem(newCustomFolder);
@@ -725,7 +730,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
                                    syncedFolder.getRemotePath(), syncedFolder.isWifiOnly(),
                                    syncedFolder.isChargingOnly(), syncedFolder.isExisting(),
                                    syncedFolder.isSubfolderByDate(), syncedFolder.getUploadAction(),
-                                   syncedFolder.isEnabled());
+                                   syncedFolder.getNameCollisionPolicy().serialize(), syncedFolder.isEnabled());
 
             saveOrUpdateSyncedFolder(item);
 
@@ -796,6 +801,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
      * @param existing        also upload existing
      * @param subfolderByDate created sub folders
      * @param uploadAction    upload action
+     * @param nameCollisionPolicy what to do on name collision
      * @param enabled         is sync enabled
      */
     private void updateSyncedFolderItem(SyncedFolderDisplayItem item,
@@ -807,6 +813,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
                                                            boolean existing,
                                                            boolean subfolderByDate,
                                                            Integer uploadAction,
+                                                           Integer nameCollisionPolicy,
                                                            boolean enabled) {
         item.setId(id);
         item.setLocalPath(localPath);
@@ -816,6 +823,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
         item.setExisting(existing);
         item.setSubfolderByDate(subfolderByDate);
         item.setUploadAction(uploadAction);
+        item.setNameCollisionPolicy(nameCollisionPolicy);
         item.setEnabled(enabled, clock.getCurrentTime());
     }
 

+ 87 - 0
src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java

@@ -39,6 +39,7 @@ import com.google.android.material.button.MaterialButton;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.MediaFolderType;
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
+import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.ui.activity.FolderPickerActivity;
 import com.owncloud.android.ui.activity.UploadFilesActivity;
@@ -69,17 +70,20 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
 
     private final static String TAG = SyncedFolderPreferencesDialogFragment.class.getSimpleName();
     private static final String BEHAVIOUR_DIALOG_STATE = "BEHAVIOUR_DIALOG_STATE";
+    private static final String NAME_COLLISION_POLICY_DIALOG_STATE = "NAME_COLLISION_POLICY_DIALOG_STATE";
     private final static float alphaEnabled = 1.0f;
     private final static float alphaDisabled = 0.7f;
 
     protected View mView;
     private CharSequence[] mUploadBehaviorItemStrings;
+    private CharSequence[] mNameCollisionPolicyItemStrings;
     private SwitchCompat mEnabledSwitch;
     private AppCompatCheckBox mUploadOnWifiCheckbox;
     private AppCompatCheckBox mUploadOnChargingCheckbox;
     private AppCompatCheckBox mUploadExistingCheckbox;
     private AppCompatCheckBox mUploadUseSubfoldersCheckbox;
     private TextView mUploadBehaviorSummary;
+    private TextView mNameCollisionPolicySummary;
     private TextView mLocalFolderPath;
     private TextView mLocalFolderSummary;
     private TextView mRemoteFolderSummary;
@@ -88,6 +92,7 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
     private MaterialButton mCancel;
     private MaterialButton mSave;
     private boolean behaviourDialogShown;
+    private boolean nameCollisionPolicyDialogShown;
     private AlertDialog behaviourDialog;
 
     public static SyncedFolderPreferencesDialogFragment newInstance(SyncedFolderDisplayItem syncedFolder, int section) {
@@ -124,6 +129,7 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
 
         mSyncedFolder = getArguments().getParcelable(SYNCED_FOLDER_PARCELABLE);
         mUploadBehaviorItemStrings = getResources().getTextArray(R.array.pref_behaviour_entries);
+        mNameCollisionPolicyItemStrings = getResources().getTextArray(R.array.pref_name_collision_policy_entries);
     }
 
     @Override
@@ -192,6 +198,8 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
 
         mUploadBehaviorSummary = view.findViewById(R.id.setting_instant_behaviour_summary);
 
+        mNameCollisionPolicySummary = view.findViewById(R.id.setting_instant_name_collision_policy_summary);
+
         mCancel = view.findViewById(R.id.cancel);
         ThemeUtils.themeDialogActionButton(mCancel);
 
@@ -228,6 +236,10 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
         mUploadUseSubfoldersCheckbox.setChecked(mSyncedFolder.isSubfolderByDate());
 
         mUploadBehaviorSummary.setText(mUploadBehaviorItemStrings[mSyncedFolder.getUploadActionInteger()]);
+
+        final int nameCollisionPolicyIndex =
+            getSelectionIndexForNameCollisionPolicy(mSyncedFolder.getNameCollisionPolicy());
+        mNameCollisionPolicySummary.setText(mNameCollisionPolicyItemStrings[nameCollisionPolicyIndex]);
     }
 
     /**
@@ -422,6 +434,14 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
                         showBehaviourDialog();
                     }
                 });
+
+        view.findViewById(R.id.setting_instant_name_collision_policy_container).setOnClickListener(
+            new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    showNameCollisionPolicyDialog();
+                }
+            });
     }
 
     private void showBehaviourDialog() {
@@ -454,6 +474,22 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
         behaviourDialog.show();
     }
 
+    private void showNameCollisionPolicyDialog() {
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+        builder.setTitle(ThemeUtils.getColoredTitle(
+                getResources().getString(R.string.pref_instant_name_collision_policy_dialogTitle),
+                ThemeUtils.primaryAccentColor(getContext())))
+            .setSingleChoiceItems(getResources().getTextArray(R.array.pref_name_collision_policy_entries),
+                                  getSelectionIndexForNameCollisionPolicy(mSyncedFolder.getNameCollisionPolicy()),
+                                  new OnNameCollisionDialogClickListener())
+            .setOnCancelListener(dialog -> nameCollisionPolicyDialogShown = false);
+
+        nameCollisionPolicyDialogShown = true;
+        behaviourDialog = builder.create();
+        behaviourDialog.show();
+    }
+
     @Override
     @NonNull
     public Dialog onCreateDialog(Bundle savedInstanceState) {
@@ -479,6 +515,7 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
     @Override
     public void onSaveInstanceState(@NonNull Bundle outState) {
         outState.putBoolean(BEHAVIOUR_DIALOG_STATE, behaviourDialogShown);
+        outState.putBoolean(NAME_COLLISION_POLICY_DIALOG_STATE, nameCollisionPolicyDialogShown);
 
         super.onSaveInstanceState(outState);
     }
@@ -487,10 +524,15 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
     public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
         behaviourDialogShown = savedInstanceState != null &&
                 savedInstanceState.getBoolean(BEHAVIOUR_DIALOG_STATE, false);
+        nameCollisionPolicyDialogShown = savedInstanceState != null &&
+            savedInstanceState.getBoolean(NAME_COLLISION_POLICY_DIALOG_STATE, false);
 
         if (behaviourDialogShown) {
             showBehaviourDialog();
         }
+        if (nameCollisionPolicyDialogShown){
+            showNameCollisionPolicyDialog();
+        }
 
         super.onViewStateRestored(savedInstanceState);
     }
@@ -526,4 +568,49 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
             ((OnSyncedFolderPreferenceListener) getActivity()).onDeleteSyncedFolderPreference(mSyncedFolder);
         }
     }
+
+    private class OnNameCollisionDialogClickListener implements DialogInterface.OnClickListener {
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            mSyncedFolder.setNameCollisionPolicy(getNameCollisionPolicyForSelectionIndex(which));
+
+            mNameCollisionPolicySummary.setText(
+                SyncedFolderPreferencesDialogFragment.this.mNameCollisionPolicyItemStrings[which]);
+            nameCollisionPolicyDialogShown = false;
+            dialog.dismiss();
+        }
+    }
+
+    /**
+     * Get index for name collision selection dialog.
+     * @return 0 if ASK_USER, 1 if OVERWRITE, 2 if RENAME. Otherwise: 0
+     */
+    static private Integer getSelectionIndexForNameCollisionPolicy(FileUploader.NameCollisionPolicy nameCollisionPolicy) {
+        switch (nameCollisionPolicy) {
+            case OVERWRITE:
+                return 1;
+            case RENAME:
+                return 2;
+            case ASK_USER:
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * Get index for name collision selection dialog.
+     * Inverse of getSelectionIndexForNameCollisionPolicy.
+     * @return ASK_USER if 0, OVERWRITE if 1, RENAME if 2. Otherwise: ASK_USEr
+     */
+    static private FileUploader.NameCollisionPolicy getNameCollisionPolicyForSelectionIndex(int index) {
+        switch (index) {
+            case 1:
+                return FileUploader.NameCollisionPolicy.OVERWRITE;
+            case 2:
+                return FileUploader.NameCollisionPolicy.RENAME;
+            case 0:
+            default:
+                return FileUploader.NameCollisionPolicy.ASK_USER;
+        }
+    }
 }

+ 13 - 0
src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java

@@ -40,6 +40,7 @@ public class SyncedFolderParcelable implements Parcelable {
     private boolean enabled = false;
     private boolean subfolderByDate = false;
     private Integer uploadAction;
+    private FileUploader.NameCollisionPolicy nameCollisionPolicy = FileUploader.NameCollisionPolicy.ASK_USER;
     private MediaFolderType type;
     private boolean hidden = false;
     private long id;
@@ -59,6 +60,8 @@ public class SyncedFolderParcelable implements Parcelable {
         type = syncedFolderDisplayItem.getType();
         account = syncedFolderDisplayItem.getAccount();
         uploadAction = syncedFolderDisplayItem.getUploadAction();
+        nameCollisionPolicy = FileUploader.NameCollisionPolicy.deserialize(
+            syncedFolderDisplayItem.getNameCollisionPolicy());
         this.section = section;
         hidden = syncedFolderDisplayItem.isHidden();
     }
@@ -76,6 +79,7 @@ public class SyncedFolderParcelable implements Parcelable {
         type = MediaFolderType.getById(read.readInt());
         account = read.readString();
         uploadAction = read.readInt();
+        nameCollisionPolicy = FileUploader.NameCollisionPolicy.deserialize(read.readInt());
         section = read.readInt();
         hidden = read.readInt() != 0;
     }
@@ -97,6 +101,7 @@ public class SyncedFolderParcelable implements Parcelable {
         dest.writeInt(type.getId());
         dest.writeString(account);
         dest.writeInt(uploadAction);
+        dest.writeInt(nameCollisionPolicy.serialize());
         dest.writeInt(section);
         dest.writeInt(hidden ? 1 : 0);
     }
@@ -182,6 +187,10 @@ public class SyncedFolderParcelable implements Parcelable {
         return this.uploadAction;
     }
 
+    public FileUploader.NameCollisionPolicy getNameCollisionPolicy() {
+        return this.nameCollisionPolicy;
+    }
+
     public MediaFolderType getType() {
         return this.type;
     }
@@ -234,6 +243,10 @@ public class SyncedFolderParcelable implements Parcelable {
         this.subfolderByDate = subfolderByDate;
     }
 
+    public void setNameCollisionPolicy(FileUploader.NameCollisionPolicy nameCollisionPolicy) {
+        this.nameCollisionPolicy = nameCollisionPolicy;
+    }
+
     public void setType(MediaFolderType type) {
         this.type = type;
     }

+ 30 - 0
src/main/res/layout/synced_folders_settings_layout.xml

@@ -381,6 +381,36 @@
                     android:text="@string/placeholder_filename"
                     android:textColor="?android:attr/textColorSecondary" />
             </LinearLayout>
+
+            <LinearLayout
+                android:id="@+id/setting_instant_name_collision_policy_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:baselineAligned="false"
+                android:clipToPadding="false"
+                android:gravity="center_vertical"
+                android:minHeight="?attr/listPreferredItemHeightSmall"
+                android:orientation="vertical"
+                android:padding="@dimen/standard_padding">
+
+                <TextView
+                    android:id="@+id/setting_instant_name_collision_policy_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:ellipsize="marquee"
+                    android:maxLines="2"
+                    android:text="@string/pref_instant_name_collision_policy_title"
+                    android:textAppearance="?attr/textAppearanceListItem" />
+
+                <TextView
+                    android:id="@+id/setting_instant_name_collision_policy_summary"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:ellipsize="end"
+                    android:maxLines="2"
+                    android:text="@string/placeholder_filename"
+                    android:textColor="?android:attr/textColorSecondary" />
+            </LinearLayout>
         </LinearLayout>
     </ScrollView>
 

+ 6 - 0
src/main/res/values/attrs.xml

@@ -11,4 +11,10 @@
 		<item>LOCAL_BEHAVIOUR_MOVE</item>
 		<item>LOCAL_BEHAVIOUR_DELETE</item>
 	</string-array>
+
+    <string-array name="pref_name_collision_policy_entries" translatable="false">
+        <item>@string/pref_instant_name_collision_policy_entries_always_ask</item>
+        <item>@string/pref_instant_name_collision_policy_entries_overwrite</item>
+        <item>@string/pref_instant_name_collision_policy_entries_rename</item>
+    </string-array>
 </resources>

+ 7 - 0
src/main/res/values/strings.xml

@@ -463,6 +463,13 @@
     <string name="pref_behaviour_entries_delete_file">deleted</string>
     <string name="prefs_storage_path">Storage path</string>
 
+    <string name="pref_instant_name_collision_policy_dialogTitle">What to do if the file already exists?</string>
+    <string name="pref_instant_name_collision_policy_title">What to do if the file already exists?</string>
+
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Ask me every time</string>
+    <string name="pref_instant_name_collision_policy_entries_rename">Rename new version</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">Overwrite remote version</string>
+
     <string name="share_dialog_title">Sharing</string>
     <string name="share_file">Share %1$s</string>
     <string name="share_with_user_section_title">Share with users and groups</string>

+ 3 - 1
src/test/java/com/owncloud/android/ui/activity/SyncedFoldersActivityTest.java

@@ -24,6 +24,7 @@ package com.owncloud.android.ui.activity;
 
 import com.owncloud.android.datamodel.MediaFolderType;
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
+import com.owncloud.android.files.services.FileUploader;
 
 import org.junit.Test;
 
@@ -166,7 +167,8 @@ public class SyncedFoldersActivityTest {
                                            true,
                                            true,
                                            "test@nextcloud.com",
-                                           1,
+                                           FileUploader.LOCAL_BEHAVIOUR_MOVE,
+                                           FileUploader.NameCollisionPolicy.ASK_USER.serialize(),
                                            enabled,
                                            System.currentTimeMillis(),
                                            new ArrayList<String>(),