Browse Source

Simplify storage selection process

Bartosz Przybylski 9 năm trước cách đây
mục cha
commit
9dc51a4880

+ 3 - 0
AndroidManifest.xml

@@ -56,6 +56,9 @@
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
 
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
     <application
         android:name=".MainApp"
         android:icon="@mipmap/ic_launcher"

+ 5 - 0
res/values/strings.xml

@@ -356,6 +356,7 @@
     <string name="uploader_upload_forbidden_permissions">to upload in this folder</string>
     <string name="downloader_download_file_not_found">The file is no longer available on the server</string>
 
+    <string name="file_migration_dialog_title">Updating storage path</string>
     <string name="file_migration_finish_button">Finish</string>
     <string name="file_migration_preparing">Preparing for migration...</string>
     <string name="file_migration_checking_destination">Checking destination...</string>
@@ -373,6 +374,10 @@
     <string name="file_migration_failed_while_coping">ERROR: While migrating</string>
     <string name="file_migration_failed_while_updating_index">ERROR: While updating index</string>
 
+    <string name="file_migration_directory_already_exists">Data folder already exists, what to do?</string>
+    <string name="file_migration_override_data_folder">Override</string>
+    <string name="file_migration_use_data_folder">Use existing</string>
+
     <string name="prefs_category_accounts">Accounts</string>
     <string name="prefs_add_account">Add account</string>
     <string name="drawer_manage_accounts">Manage accounts</string>

+ 1 - 1
res/xml/preferences.xml

@@ -19,7 +19,7 @@
 -->
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
 	<PreferenceCategory android:title="@string/prefs_category_general">
-		<com.owncloud.android.ui.PreferenceWithLongSummary
+		<ListPreference
 			android:title="@string/prefs_storage_path"
 			android:key="storage_path" />
 	</PreferenceCategory>

+ 2 - 1
src/com/owncloud/android/MainApp.java

@@ -35,6 +35,7 @@ import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory.Policy;
 import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.ui.activity.Preferences;
 
 
 /**
@@ -67,7 +68,7 @@ public class MainApp extends Application {
 
         SharedPreferences appPrefs =
                 PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
-        MainApp.storagePath = appPrefs.getString("storage_path", Environment.
+        MainApp.storagePath = appPrefs.getString(Preferences.Keys.STORAGE_PATH, Environment.
                               getExternalStorageDirectory().getAbsolutePath());
 
         boolean isSamlAuth = AUTH_ON.equals(getString(R.string.auth_method_saml_web_sso));

+ 65 - 39
src/com/owncloud/android/ui/activity/Preferences.java

@@ -35,6 +35,7 @@ import android.os.Environment;
 import android.os.Handler;
 import android.os.IBinder;
 import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
 import android.preference.Preference;
 import android.preference.Preference.OnPreferenceChangeListener;
 import android.preference.Preference.OnPreferenceClickListener;
@@ -64,12 +65,11 @@ import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.services.OperationsService;
 import com.owncloud.android.ui.PreferenceWithLongSummary;
 import com.owncloud.android.ui.RadioButtonPreference;
+import com.owncloud.android.utils.DataStorageUtils;
 import com.owncloud.android.utils.DisplayUtils;
 
-import java.io.File;
 import java.io.IOException;
 
-
 /**
  * An Activity that allows the user to change the application's settings.
  *
@@ -118,6 +118,10 @@ public class Preferences extends PreferenceActivity {
     private PreferenceWithLongSummary mPrefStoragePath;
     private String mStoragePath;
 
+	public static class Keys {
+		public static final String STORAGE_PATH = "storage_path";
+	}
+
     @SuppressWarnings("deprecation")
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -140,7 +144,6 @@ public class Preferences extends PreferenceActivity {
             getWindow().getDecorView().findViewById(actionBarTitleId).
                     setContentDescription(getString(R.string.actionbar_settings));
         }
-        
         // Load package info
         String temp;
         try {
@@ -149,9 +152,9 @@ public class Preferences extends PreferenceActivity {
         } catch (NameNotFoundException e) {
             temp = "";
             Log_OC.e(TAG, "Error while showing about dialog", e);
-        } 
+        }
         final String appVersion = temp;
-       
+
         // Register context menu for list of preferences.
         registerForContextMenu(getListView());
 
@@ -237,7 +240,7 @@ public class Preferences extends PreferenceActivity {
                 preferenceCategory.removePreference(pHelp);
             }
         }
-        
+
        boolean recommendEnabled = getResources().getBoolean(R.bool.recommend_enabled);
        Preference pRecommend =  findPreference("recommend");
         if (pRecommend != null){
@@ -246,11 +249,11 @@ public class Preferences extends PreferenceActivity {
                     @Override
                     public boolean onPreferenceClick(Preference preference) {
 
-                        Intent intent = new Intent(Intent.ACTION_SENDTO); 
+                        Intent intent = new Intent(Intent.ACTION_SENDTO);
                         intent.setType("text/plain");
-                        intent.setData(Uri.parse(getString(R.string.mail_recommend))); 
-                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
-                        
+                        intent.setData(Uri.parse(getString(R.string.mail_recommend)));
+                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
                         String appName = getString(R.string.app_name);
                         String downloadUrl = getString(R.string.url_app_download);
 
@@ -259,7 +262,7 @@ public class Preferences extends PreferenceActivity {
                                 appName);
                         String recommendText = String.format(getString(R.string.recommend_text),
                                 appName, downloadUrl);
-                        
+
                         intent.putExtra(Intent.EXTRA_SUBJECT, recommendSubject);
                         intent.putExtra(Intent.EXTRA_TEXT, recommendText);
                         startActivity(intent);
@@ -272,7 +275,7 @@ public class Preferences extends PreferenceActivity {
                 preferenceCategory.removePreference(pRecommend);
             }
         }
-        
+
         boolean feedbackEnabled = getResources().getBoolean(R.bool.feedback_enabled);
         Preference pFeedback =  findPreference("feedback");
         if (pFeedback != null){
@@ -286,11 +289,11 @@ public class Preferences extends PreferenceActivity {
                         Intent intent = new Intent(Intent.ACTION_SENDTO); 
                         intent.setType("text/plain");
                         intent.putExtra(Intent.EXTRA_SUBJECT, feedback);
-                        
-                        intent.setData(Uri.parse(feedbackMail)); 
-                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
+
+                        intent.setData(Uri.parse(feedbackMail));
+                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                         startActivity(intent);
-                        
+
                         return true;
                     }
                 });
@@ -339,26 +342,35 @@ public class Preferences extends PreferenceActivity {
             }
         }
 
-        mPrefStoragePath =  (PreferenceWithLongSummary)findPreference("storage_path");
+        mPrefStoragePath =  (ListPreference) findPreference(Keys.STORAGE_PATH);
         if (mPrefStoragePath != null) {
-
-            mPrefStoragePath.setOnPreferenceClickListener(new OnPreferenceClickListener() {
-                @Override
-                public boolean onPreferenceClick(Preference preference) {
-                    Intent intent = new Intent(Preferences.this, LocalDirectorySelectorActivity.class);
-                    intent.putExtra(UploadFilesActivity.KEY_DIRECTORY_PATH, mStoragePath);
-                    startActivityForResult(intent, ACTION_SELECT_STORAGE_PATH);
-                    return true;
-                }
-            });
+			DataStorageUtils.Storage[] storageOptions = DataStorageUtils.getAvailableStoragePoints(getApplicationContext());
+			String[] entries = new String[storageOptions.length];
+			String[] values = new String[storageOptions.length];
+			for (int i = 0; i < storageOptions.length; ++i) {
+				entries[i] = storageOptions[i].getDescription();
+				values[i] = storageOptions[i].getPath();
+			}
+			mPrefStoragePath.setEntries(entries);
+			mPrefStoragePath.setEntryValues(values);
 
             mPrefStoragePath.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
                     @Override
                     public boolean onPreferenceChange(Preference preference, Object newValue) {
-                        MainApp.setStoragePath((String) newValue);
+						String newPath = (String)newValue;
+						if (mStoragePath.equals(newPath))
+							return true;
+
+						StorageMigration storageMigration = new StorageMigration(Preferences.this, mStoragePath, newPath);
+
+						storageMigration.setStorageMigrationProgressListener(Preferences.this);
+
+						storageMigration.migrate();
+
                         return true;
                     }
                 });
+
         }
 
         mPrefInstantUploadPath = (PreferenceWithLongSummary)findPreference("instant_upload_path");
@@ -377,7 +389,7 @@ public class Preferences extends PreferenceActivity {
                     }
                 });
         }
-        
+
         mPrefInstantUploadCategory =
                 (PreferenceCategory) findPreference("instant_uploading_category");
 
@@ -385,11 +397,11 @@ public class Preferences extends PreferenceActivity {
         mPrefInstantUploadPathWiFi =  findPreference("instant_upload_on_wifi");
         mPrefInstantPictureUploadOnlyOnCharging = findPreference("instant_upload_on_charging");
         mPrefInstantUpload = findPreference("instant_uploading");
-        
+
         toggleInstantPictureOptions(((CheckBoxPreference) mPrefInstantUpload).isChecked());
-        
+
         mPrefInstantUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
-            
+
             @Override
             public boolean onPreferenceChange(Preference preference, Object newValue) {
                 toggleInstantPictureOptions((Boolean) newValue);
@@ -399,7 +411,7 @@ public class Preferences extends PreferenceActivity {
                 return true;
             }
         });
-       
+
         mPrefInstantVideoUploadPath =  findPreference("instant_video_upload_path");
         if (mPrefInstantVideoUploadPath != null){
 
@@ -423,7 +435,7 @@ public class Preferences extends PreferenceActivity {
         mPrefInstantVideoUpload = findPreference("instant_video_uploading");
         mPrefInstantVideoUploadOnlyOnCharging = findPreference("instant_video_upload_on_charging");
         toggleInstantVideoOptions(((CheckBoxPreference) mPrefInstantVideoUpload).isChecked());
-        
+
         mPrefInstantVideoUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
 
             @Override
@@ -523,7 +535,7 @@ public class Preferences extends PreferenceActivity {
             mPrefInstantUploadCategory.removePreference(mPrefInstantPictureUploadOnlyOnCharging);
         }
     }
-    
+
     private void toggleInstantVideoOptions(Boolean value){
         if (value){
             mPrefInstantUploadCategory.addPreference(mPrefInstantVideoUploadPathWiFi);
@@ -747,9 +759,10 @@ public class Preferences extends PreferenceActivity {
         mStoragePath = newStoragePath;
         MainApp.setStoragePath(mStoragePath);
         SharedPreferences.Editor editor = appPrefs.edit();
-        editor.putString("storage_path", mStoragePath);
+        editor.putString(Keys.STORAGE_PATH, mStoragePath);
         editor.commit();
-        mPrefStoragePath.setSummary(mStoragePath);
+		String storageDescription = DataStorageUtils.getStorageDescriptionByPath(mStoragePath, this);
+        mPrefStoragePath.setSummary(storageDescription);
     }
 
     /**
@@ -758,9 +771,10 @@ public class Preferences extends PreferenceActivity {
     private void loadStoragePath() {
         SharedPreferences appPrefs =
                 PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
-        mStoragePath = appPrefs.getString("storage_path", Environment.getExternalStorageDirectory()
+        mStoragePath = appPrefs.getString(Keys.STORAGE_PATH, Environment.getExternalStorageDirectory()
                                                          .getAbsolutePath());
-        mPrefStoragePath.setSummary(mStoragePath);
+		String storageDescription = DataStorageUtils.getStorageDescriptionByPath(mStoragePath, getApplicationContext());
+		mPrefStoragePath.setSummary(storageDescription);
     }
 
     /**
@@ -791,4 +805,16 @@ public class Preferences extends PreferenceActivity {
         editor.putString("instant_video_upload_path", mUploadVideoPath);
         editor.commit();
     }
+
+	@Override
+	public void onStorageMigrationFinished(String storagePath, boolean succeed) {
+		if (succeed) {
+			saveStoragePath(storagePath);
+		}
+	}
+
+	@Override
+	public void onCancelMigration() {
+		// Migration was canceled so we don't do anything
+	}
 }

+ 391 - 0
src/com/owncloud/android/ui/activity/StorageMigration.java

@@ -0,0 +1,391 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author Bartosz Przybylski
+ *   Copyright (C) 2015 ownCloud Inc.
+ *   Copyright (C) 2015 Bartosz Przybylski
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.activity;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.ProgressDialog;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.AsyncTask;
+import android.support.v7.app.AlertDialog;
+
+import com.owncloud.android.MainApp;
+import com.owncloud.android.R;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.utils.FileStorageUtils;
+
+import java.io.File;
+
+/**
+ * Created by Bartosz Przybylski on 07.11.2015.
+ */
+public class StorageMigration {
+	private static final String TAG = StorageMigration.class.getName();
+
+	public interface StorageMigrationProgressListener {
+		void onStorageMigrationFinished(String storagePath, boolean succeed);
+		void onCancelMigration();
+	}
+
+	private ProgressDialog mProgressDialog;
+	private Context mContext;
+	private String mSourceStoragePath;
+	private String mTargetStoragePath;
+
+	private StorageMigrationProgressListener mListener;
+
+	public StorageMigration(Context context, String sourcePath, String targetPath) {
+		mContext = context;
+		mSourceStoragePath = sourcePath;
+		mTargetStoragePath = targetPath;
+	}
+
+	public void setStorageMigrationProgressListener(StorageMigrationProgressListener listener) {
+		mListener = listener;
+	}
+
+	public void migrate() {
+		if (storageFolderAlreadyExists())
+			askToOverride();
+		else {
+			AlertDialog progressDialog = createMigrationProgressDialog();
+			progressDialog.show();
+			new FileMigrationTask(
+					mContext,
+					mSourceStoragePath,
+					mTargetStoragePath,
+					progressDialog,
+					mListener).execute();
+
+			progressDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
+		}
+	}
+
+	private boolean storageFolderAlreadyExists() {
+		File f = new File(mTargetStoragePath, MainApp.getDataFolder());
+		return f.exists() && f.isDirectory();
+	}
+
+	private void askToOverride() {
+
+		new AlertDialog.Builder(mContext)
+				.setMessage(R.string.file_migration_directory_already_exists)
+				.setCancelable(true)
+				.setOnCancelListener(new DialogInterface.OnCancelListener() {
+					@Override
+					public void onCancel(DialogInterface dialogInterface) {
+						if (mListener != null)
+							mListener.onCancelMigration();
+					}
+				})
+				.setNegativeButton(R.string.common_cancel, new OnClickListener() {
+					@Override
+					public void onClick(DialogInterface dialogInterface, int i) {
+						if (mListener != null)
+							mListener.onCancelMigration();
+					}
+				})
+				.setNeutralButton(R.string.file_migration_use_data_folder, new OnClickListener() {
+					@Override
+					public void onClick(DialogInterface dialogInterface, int i) {
+						AlertDialog progressDialog = createMigrationProgressDialog();
+						progressDialog.show();
+						new StoragePathSwitchTask(
+								mContext,
+								mSourceStoragePath,
+								mTargetStoragePath,
+								progressDialog,
+								mListener).execute();
+
+						progressDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
+
+					}
+				})
+				.setPositiveButton(R.string.file_migration_override_data_folder, new OnClickListener() {
+					@Override
+					public void onClick(DialogInterface dialogInterface, int i) {
+						AlertDialog progressDialog = createMigrationProgressDialog();
+						progressDialog.show();
+						new FileMigrationTask(
+								mContext,
+								mSourceStoragePath,
+								mTargetStoragePath,
+								progressDialog,
+								mListener).execute();
+
+						progressDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
+					}
+				})
+				.create()
+				.show();
+	}
+
+	private AlertDialog createMigrationProgressDialog() {
+		AlertDialog progressDialog = new AlertDialog.Builder(mContext)
+				.setCancelable(false)
+				.setTitle(R.string.file_migration_dialog_title)
+				.setMessage(R.string.file_migration_preparing)
+				.setPositiveButton(R.string.drawer_close, new OnClickListener() {
+					@Override
+					public void onClick(DialogInterface dialogInterface, int i) {
+						dialogInterface.dismiss();
+					}
+				})
+				.create();
+		return progressDialog;
+	}
+
+	abstract static private class FileMigrationTaskBase extends AsyncTask<Void, Integer, Integer> {
+		protected String mStorageSource;
+		protected String mStorageTarget;
+		protected Context mContext;
+		protected AlertDialog mProgressDialog;
+		protected StorageMigrationProgressListener mListener;
+
+		protected String mAuthority;
+		protected Account[] mOcAccounts;
+
+		public FileMigrationTaskBase(Context context, String source, String target, AlertDialog progressDialog, StorageMigrationProgressListener listener) {
+			mContext = context;
+			mStorageSource = source;
+			mStorageTarget = target;
+			mProgressDialog = progressDialog;
+			mListener = listener;
+
+			mAuthority = mContext.getString(R.string.authority);
+			mOcAccounts = AccountManager.get(mContext).getAccountsByType(MainApp.getAccountType());
+		}
+
+		@Override
+		protected void onProgressUpdate(Integer... progress) {
+			if (progress.length > 1 && progress[0] != 0)
+				mProgressDialog.setMessage(mContext.getString(progress[0]));
+		}
+
+		@Override
+		protected void onPostExecute(Integer code) {
+			if (code != 0) {
+				mProgressDialog.setMessage(mContext.getString(code));
+			} else {
+				mProgressDialog.setMessage(mContext.getString(R.string.file_migration_ok_finished));
+			}
+			mProgressDialog.getButton(android.app.AlertDialog.BUTTON_POSITIVE).setEnabled(true);
+			boolean succeed = code == 0;
+			if (mListener != null)
+				mListener.onStorageMigrationFinished(succeed ? mStorageTarget : mStorageSource, succeed);
+		}
+
+		protected boolean[] saveAccountsSyncStatus() {
+			boolean[] syncs = new boolean[mOcAccounts.length];
+			for (int i = 0; i < mOcAccounts.length; ++i)
+				syncs[i] = ContentResolver.getSyncAutomatically(mOcAccounts[i], mAuthority);
+			return syncs;
+		}
+
+		protected void stopAccountsSyncing() {
+			for (int i = 0; i < mOcAccounts.length; ++i)
+				ContentResolver.setSyncAutomatically(mOcAccounts[i], mAuthority, false);
+		}
+
+		protected void waitForUnfinishedSynchronizations() {
+			for (int i = 0; i < mOcAccounts.length; ++i)
+				while (ContentResolver.isSyncActive(mOcAccounts[i], mAuthority))
+					try {
+						Thread.sleep(1000);
+					} catch (InterruptedException e) {
+						Log_OC.w(TAG, "Thread interrupted while waiting for account to end syncing");
+						Thread.currentThread().interrupt();
+					}
+		}
+
+		protected void restoreAccountsSyncStatus(boolean oldSync[]) {
+			for (int i = 0; i < mOcAccounts.length; ++i)
+				ContentResolver.setSyncAutomatically(mOcAccounts[i], mAuthority, oldSync[i]);
+		}
+	}
+
+	static private class StoragePathSwitchTask extends FileMigrationTaskBase {
+
+		public StoragePathSwitchTask(Context context, String source, String target, AlertDialog progressDialog, StorageMigrationProgressListener listener) {
+			super(context, source, target, progressDialog, listener);
+		}
+
+		@Override
+		protected Integer doInBackground(Void... voids) {
+			publishProgress(R.string.file_migration_preparing);
+
+			Log_OC.stopLogging();
+			boolean[] syncStates = new boolean[0];
+			try {
+				publishProgress(R.string.file_migration_saving_accounts_configuration);
+				syncStates = saveAccountsSyncStatus();
+
+				publishProgress(R.string.file_migration_waiting_for_unfinished_sync);
+				stopAccountsSyncing();
+				waitForUnfinishedSynchronizations();
+			} finally {
+				publishProgress(R.string.file_migration_restoring_accounts_configuration);
+				restoreAccountsSyncStatus(syncStates);
+			}
+			Log_OC.startLogging(mStorageTarget);
+
+			return 0;
+		}
+	}
+
+	static private class FileMigrationTask extends FileMigrationTaskBase {
+		private class MigrationException extends Exception {
+			private int mResId;
+
+			MigrationException(int resId) {
+				super();
+				this.mResId = resId;
+			}
+
+			int getResId() { return mResId; }
+		}
+
+		public FileMigrationTask(Context context, String source, String target, AlertDialog progressDialog, StorageMigrationProgressListener listener) {
+			super(context, source, target, progressDialog, listener);
+		}
+
+		@Override
+		protected Integer doInBackground(Void... args) {
+			publishProgress(R.string.file_migration_preparing);
+			Log_OC.stopLogging();
+
+			boolean[] syncState = new boolean[0];
+
+			try {
+				File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
+				deleteRecursive(dstFile);
+				dstFile.delete();
+
+				publishProgress(R.string.file_migration_checking_destination);
+
+				checkDestinationAvailability();
+
+				publishProgress(R.string.file_migration_saving_accounts_configuration);
+				syncState = saveAccountsSyncStatus();
+
+				publishProgress(R.string.file_migration_waiting_for_unfinished_sync);
+				stopAccountsSyncing();
+				waitForUnfinishedSynchronizations();
+
+				publishProgress(R.string.file_migration_migrating);
+				copyFiles();
+
+				publishProgress(R.string.file_migration_updating_index);
+				updateIndex(mContext);
+
+				publishProgress(R.string.file_migration_cleaning);
+				cleanup();
+
+			} catch (MigrationException e) {
+				rollback();
+				Log_OC.startLogging(mStorageSource);
+				return e.getResId();
+			} finally {
+				publishProgress(R.string.file_migration_restoring_accounts_configuration);
+				restoreAccountsSyncStatus(syncState);
+			}
+
+			Log_OC.startLogging(mStorageTarget);
+			publishProgress(R.string.file_migration_ok_finished);
+
+			return 0;
+		}
+
+
+		void checkDestinationAvailability() throws MigrationException {
+			File srcFile = new File(mStorageSource);
+			File dstFile = new File(mStorageTarget);
+
+			if (!dstFile.canRead() || !srcFile.canRead())
+				throw new MigrationException(R.string.file_migration_failed_not_readable);
+
+			if (!dstFile.canWrite() || !srcFile.canWrite())
+				throw new MigrationException(R.string.file_migration_failed_not_writable);
+
+			if (new File(dstFile, MainApp.getDataFolder()).exists())
+				throw new MigrationException(R.string.file_migration_failed_dir_already_exists);
+
+			if (dstFile.getFreeSpace() < FileStorageUtils.getFolderSize(new File(srcFile, MainApp.getDataFolder())))
+				throw new MigrationException(R.string.file_migration_failed_not_enough_space);
+		}
+
+		void copyFiles() throws MigrationException {
+			File srcFile = new File(mStorageSource + File.separator + MainApp.getDataFolder());
+			File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
+
+			copyDirs(srcFile, dstFile);
+		}
+
+		void copyDirs(File src, File dst) throws MigrationException {
+			if (!dst.mkdirs())
+				throw new MigrationException(R.string.file_migration_failed_while_coping);
+
+			for (File f : src.listFiles()) {
+				if (f.isDirectory())
+					copyDirs(f, new File(dst, f.getName()));
+				else if (!FileStorageUtils.copyFile(f, new File(dst, f.getName())))
+					throw new MigrationException(R.string.file_migration_failed_while_coping);
+			}
+
+		}
+
+		void updateIndex(Context context) throws MigrationException {
+			FileDataStorageManager manager = new FileDataStorageManager(null, context.getContentResolver());
+
+			try {
+				manager.migrateStoredFiles(mStorageSource, mStorageTarget);
+			} catch (Exception e) {
+				throw new MigrationException(R.string.file_migration_failed_while_updating_index);
+			}
+		}
+
+		void cleanup() {
+			File srcFile = new File(mStorageSource + File.separator + MainApp.getDataFolder());
+			if (!deleteRecursive(srcFile))
+				Log_OC.w(TAG, "Migration cleanup step failed");
+			srcFile.delete();
+		}
+
+		boolean deleteRecursive(File f) {
+			boolean res = true;
+			if (f.isDirectory())
+				for (File c : f.listFiles())
+					res = deleteRecursive(c) && res;
+			return f.delete() && res;
+		}
+
+		void rollback() {
+			File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
+			if (dstFile.exists())
+				if (!dstFile.delete())
+					Log_OC.w(TAG, "Rollback step failed");
+		}
+	}
+}

+ 0 - 290
src/com/owncloud/android/ui/activity/StorageMigrationActivity.java

@@ -1,290 +0,0 @@
-/**
- *   ownCloud Android client application
- *
- *   @author Bartosz Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
- *   Copyright (C) 2015 Bartosz Przybylski
- *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License version 2,
- *   as published by the Free Software Foundation.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-package com.owncloud.android.ui.activity;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.support.v7.app.AppCompatActivity;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import com.owncloud.android.MainApp;
-import com.owncloud.android.R;
-import com.owncloud.android.datamodel.FileDataStorageManager;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.utils.FileStorageUtils;
-
-import java.io.File;
-
-/**
- * Created by Bartosz Przybylski on 07.11.2015.
- */
-public class StorageMigrationActivity extends AppCompatActivity {
-	private static final String TAG = StorageMigrationActivity.class.getName();
-	public static final String KEY_MIGRATION_TARGET_DIR = "MIGRATION_TARGET";
-	public static final String KEY_MIGRATION_SOURCE_DIR = "MIGRATION_SOURCE";
-
-	private ProgressBar mProgressBar;
-	private Button mFinishButton;
-	private TextView mFeedbackText;
-
-	@Override
-	public void onCreate(Bundle savedInstanceState) {
-		super.onCreate(savedInstanceState);
-
-		setContentView(R.layout.migration_layout);
-		mProgressBar = (ProgressBar)findViewById(R.id.migrationProgress);
-		mFinishButton = (Button)findViewById(R.id.finishButton);
-		mFeedbackText = (TextView)findViewById(R.id.migrationText);
-
-		mProgressBar.setProgress(0);
-		mFinishButton.setVisibility(View.INVISIBLE);
-		mFeedbackText.setText(R.string.file_migration_preparing);
-
-		mFinishButton.setOnClickListener(new View.OnClickListener() {
-			@Override
-			public void onClick(View view) {
-				setResult(RESULT_CANCELED);
-				finish();
-			}
-		});
-
-		String source = getIntent().getStringExtra(KEY_MIGRATION_SOURCE_DIR);
-		String destination = getIntent().getStringExtra(KEY_MIGRATION_TARGET_DIR);
-
-		if (source == null || destination == null) {
-			Log_OC.e(TAG, "source or destination is null");
-			finish();
-		}
-
-		new FileMigrationTask().execute(source, destination);
-	}
-
-	private class FileMigrationTask extends AsyncTask<String, Integer, Integer> {
-
-		private String mStorageTarget;
-		private String mStorageSource;
-		private int mProgress;
-
-		private static final int mProgressCopyUpperBound = 98;
-
-		private class MigrationException extends Exception {
-			private int mResId;
-			/*
-			 * @param resId resource identifier to use for displaying error
-			 */
-			MigrationException(int resId) {
-				super();
-				this.mResId = resId;
-			}
-
-			int getResId() { return mResId; }
-		}
-
-		@Override
-		protected Integer doInBackground(String... args) {
-
-			mStorageSource = args[0];
-			mStorageTarget = args[1];
-			mProgress = 0;
-
-			publishProgress(mProgress++, R.string.file_migration_preparing);
-
-			Context context = StorageMigrationActivity.this;
-			String ocAuthority = context.getString(R.string.authority);
-
-			Account[] ocAccounts = AccountManager.get(context).getAccountsByType(MainApp.getAccountType());
-			boolean[] oldAutoSync = new boolean[ocAccounts.length];
-
-			Log_OC.stopLogging();
-
-			try {
-				publishProgress(mProgress++, R.string.file_migration_checking_destination);
-
-				checkDestinationAvailability();
-
-				publishProgress(mProgress++, R.string.file_migration_saving_accounts_configuration);
-				saveAccountsSyncStatus(ocAuthority, ocAccounts, oldAutoSync);
-
-				publishProgress(mProgress++, R.string.file_migration_waiting_for_unfinished_sync);
-				stopAccountsSyncing(ocAuthority, ocAccounts);
-				waitForUnfinishedSynchronizations(ocAuthority, ocAccounts);
-
-				publishProgress(mProgress++, R.string.file_migration_migrating);
-				copyFiles();
-
-				publishProgress(mProgress++, R.string.file_migration_updating_index);
-				updateIndex(context);
-
-				publishProgress(mProgress++, R.string.file_migration_cleaning);
-				cleanup();
-
-			} catch (MigrationException e) {
-				rollback();
-				Log_OC.startLogging(mStorageSource);
-				return e.getResId();
-			} finally {
-				publishProgress(mProgress++, R.string.file_migration_restoring_accounts_configuration);
-				restoreAccountsSyncStatus(ocAuthority, ocAccounts, oldAutoSync);
-			}
-
-			Log_OC.startLogging(mStorageTarget);
-			publishProgress(mProgress++, R.string.file_migration_ok_finished);
-
-			return 0;
-		}
-
-		@Override
-		protected void onProgressUpdate(Integer... progress) {
-			mProgressBar.setProgress(progress[0]);
-			if (progress.length > 1)
-				mFeedbackText.setText(progress[1]);
-		}
-
-		@Override
-		protected void onPostExecute(Integer code) {
-			mFinishButton.setVisibility(View.VISIBLE);
-			if (code != 0) {
-				mFeedbackText.setText(code);
-			} else {
-				mFeedbackText.setText(R.string.file_migration_ok_finished);
-				mFinishButton.setOnClickListener(new View.OnClickListener() {
-					@Override
-					public void onClick(View view) {
-						Intent resultIntent = new Intent();
-						resultIntent.putExtra(KEY_MIGRATION_TARGET_DIR, mStorageTarget);
-						setResult(RESULT_OK, resultIntent);
-						finish();
-					}
-				});
-			}
-		}
-
-		void checkDestinationAvailability() throws MigrationException {
-			File srcFile = new File(mStorageSource);
-			File dstFile = new File(mStorageTarget);
-
-			if (!dstFile.canRead() || !srcFile.canRead())
-				throw new MigrationException(R.string.file_migration_failed_not_readable);
-
-			if (!dstFile.canWrite() || !srcFile.canWrite())
-				throw new MigrationException(R.string.file_migration_failed_not_writable);
-
-			if (new File(dstFile, MainApp.getDataFolder()).exists())
-				throw new MigrationException(R.string.file_migration_failed_dir_already_exists);
-
-			if (dstFile.getFreeSpace() < FileStorageUtils.getFolderSize(new File(srcFile, MainApp.getDataFolder())))
-				throw new MigrationException(R.string.file_migration_failed_not_enough_space);
-		}
-
-		void copyFiles() throws MigrationException {
-			File srcFile = new File(mStorageSource + File.separator + MainApp.getDataFolder());
-			File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
-
-			copyDirs(srcFile, dstFile);
-			mProgress = Math.max(mProgress, mProgressCopyUpperBound);
-			publishProgress(mProgress);
-		}
-
-		void copyDirs(File src, File dst) throws MigrationException {
-			if (!dst.mkdirs())
-				throw new MigrationException(R.string.file_migration_failed_while_coping);
-
-			for (File f : src.listFiles()) {
-
-				mProgress = Math.min(mProgress+1, mProgressCopyUpperBound);
-				publishProgress(mProgress);
-
-				if (f.isDirectory())
-					copyDirs(f, new File(dst, f.getName()));
-				else if (!FileStorageUtils.copyFile(f, new File(dst, f.getName())))
-					throw new MigrationException(R.string.file_migration_failed_while_coping);
-			}
-
-		}
-
-		void updateIndex(Context context) throws MigrationException {
-			FileDataStorageManager manager = new FileDataStorageManager(null, context.getContentResolver());
-
-			try {
-				manager.migrateStoredFiles(mStorageSource, mStorageTarget);
-			} catch (Exception e) {
-				throw new MigrationException(R.string.file_migration_failed_while_updating_index);
-			}
-		}
-
-		void cleanup() {
-			File srcFile = new File(mStorageSource + File.separator + MainApp.getDataFolder());
-			if (!deleteRecursive(srcFile))
-				Log_OC.w(TAG, "Migration cleanup step failed");
-		}
-
-		boolean deleteRecursive(File f) {
-			boolean res = true;
-			if (f.isDirectory())
-				for (File c : f.listFiles())
-					res = deleteRecursive(c) && res;
-			return f.delete() && res;
-		}
-
-
-		void rollback() {
-			File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
-			if (dstFile.exists())
-				if (!dstFile.delete())
-					Log_OC.w(TAG, "Rollback step failed");
-		}
-
-		void saveAccountsSyncStatus(String authority, Account accounts[], boolean syncs[]) {
-			for (int i = 0; i < accounts.length; ++i)
-				syncs[i] = ContentResolver.getSyncAutomatically(accounts[i], authority);
-		}
-
-		void stopAccountsSyncing(String authority, Account accounts[]) {
-			for (int i = 0; i < accounts.length; ++i)
-				ContentResolver.setSyncAutomatically(accounts[i], authority, false);
-		}
-
-		void waitForUnfinishedSynchronizations(String authority, Account accounts[]) {
-			for (int i = 0; i < accounts.length; ++i)
-				while (ContentResolver.isSyncActive(accounts[i], authority))
-					try {
-						Thread.sleep(1000);
-					} catch (InterruptedException e) {
-						Log_OC.w(TAG, "Thread interrupted while waiting for account to end syncing");
-						Thread.currentThread().interrupt();
-					}
-		}
-
-		void restoreAccountsSyncStatus(String authority, Account accounts[], boolean oldSync[]) {
-			for (int i = 0; i < accounts.length; ++i)
-				ContentResolver.setSyncAutomatically(accounts[i], authority, oldSync[i]);
-		}
-
-	}
-}

+ 101 - 1
src/com/owncloud/android/utils/DataStorageUtils.java

@@ -1,7 +1,107 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author Bartek Przybylski
+ *   Copyright (C) 2015  Bartosz Przybylski
+ *   Copyright (C) 2015 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
 package com.owncloud.android.utils;
 
+import android.content.Context;
+import android.os.Build;
+import android.os.Environment;
+
+import com.owncloud.android.R;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
 /**
- * Created by bartek on 06.12.2015.
+ * @author Bartosz Przybylski
  */
 public class DataStorageUtils {
+	static public final class Storage {
+		private String mDescription;
+		private String mPath;
+
+		Storage(String description, String path) {
+			mDescription = description;
+			mPath = path;
+		}
+
+		public String getPath() { return mPath; }
+		public String getDescription() { return mDescription; }
+	}
+
+	static public Storage[] getAvailableStoragePoints(Context context) {
+		// Android 6.0 and later changed some internal implementation of accessing storage
+		// so for now we return only single path named "external"
+		// TODO(przybylski): add 6.0 code
+		if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
+			return new Storage[] {
+				new Storage(context.getString(R.string.storage_description_external),
+						    Environment.getExternalStorageDirectory().getAbsolutePath())
+			};
+		}
+
+		List<Storage> list = new ArrayList<>();
+
+		String storagePath = Environment.getExternalStorageDirectory().getPath();
+		if (canBeAddedToAvailableList(storagePath))
+			list.add(new Storage(context.getString(R.string.storage_description_external),
+					 			 storagePath));
+
+		storagePath = System.getenv("SECONDARY_STORAGE"); // : separated paths to sd cards
+		list.addAll(getSDCardStorage(storagePath, context));
+
+		//list.add(new Storage("Costam", Environment.getExternalStorageDirectory().getAbsolutePath() + "/costam"));
+
+		return list.toArray(new Storage[list.size()]);
+	}
+
+	static public String getStorageDescriptionByPath(String path, Context context) {
+		Storage[] storages = getAvailableStoragePoints(context);
+		for (Storage s : storages) {
+			if (s.getPath().equals(path))
+				return s.getDescription();
+		}
+		return context.getString(R.string.storage_description_unknown);
+	}
+
+	private static List<Storage> getSDCardStorage(String storagePath, Context context) {
+		if (storagePath == null) return new ArrayList<>();
+
+		String[] paths = storagePath.split(":");
+		List<Storage> list = new ArrayList<>();
+		if (paths.length == 1)
+			list.add(new Storage(context.getString(R.string.storage_description_sd), paths[0]));
+		else
+			for (int i = 0; i < paths.length; ++i) {
+				if (canBeAddedToAvailableList(paths[i]))
+					list.add(new Storage(context.getString(R.string.storage_description_sd_no, i+1),
+							paths[i]));
+			}
+		return list;
+	}
+
+	private static boolean canBeAddedToAvailableList(String path) {
+		if (path == null) return false;
+		File f = new File(path);
+		return f.exists() && f.isDirectory();
+	}
 }