diff --git a/.classpath b/.classpath
index 51769745b2c..72895a2990d 100644
--- a/.classpath
+++ b/.classpath
@@ -3,7 +3,7 @@
-
+
diff --git a/.gitignore b/.gitignore
index 8346dbfca36..cf93098389e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ oc_workaround/local.properties
oc_framework/local.properties
oc_framework-test-project/local.properties
tests/local.properties
+lint.xml
# Mac .DS_Store files
.DS_Store
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index fc701a94214..b47ae4ad9f3 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -149,7 +149,7 @@
-
+
@@ -158,8 +158,18 @@
+
+
+
-
+
+
+
+
+
+
+
@@ -171,9 +181,6 @@
-
-
-
diff --git a/res/drawable/btn_small_round.xml b/res/drawable/btn_small_round.xml
new file mode 100755
index 00000000000..6638ed9e25d
--- /dev/null
+++ b/res/drawable/btn_small_round.xml
@@ -0,0 +1,46 @@
+
+
+
+
+ -
+
+
+
-
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
-
+
+
+
+ -
+
+
+
+
+ -
+
+
+
-
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/upload_failed.png b/res/drawable/upload_failed.png
new file mode 100755
index 00000000000..86d00c586f5
Binary files /dev/null and b/res/drawable/upload_failed.png differ
diff --git a/res/drawable/upload_finished.png b/res/drawable/upload_finished.png
new file mode 100755
index 00000000000..ed7f7967f66
Binary files /dev/null and b/res/drawable/upload_finished.png differ
diff --git a/res/drawable/upload_in_progress.png b/res/drawable/upload_in_progress.png
new file mode 100755
index 00000000000..f30649472b2
Binary files /dev/null and b/res/drawable/upload_in_progress.png differ
diff --git a/res/layout/errorhandling_showerror.xml b/res/layout/errorhandling_showerror.xml
new file mode 100755
index 00000000000..14bb522808a
--- /dev/null
+++ b/res/layout/errorhandling_showerror.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/list_fragment_expandable.xml b/res/layout/list_fragment_expandable.xml
new file mode 100755
index 00000000000..838e48efdbb
--- /dev/null
+++ b/res/layout/list_fragment_expandable.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/upload_list_group.xml b/res/layout/upload_list_group.xml
new file mode 100755
index 00000000000..8584c0d74d9
--- /dev/null
+++ b/res/layout/upload_list_group.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/upload_list_item.xml b/res/layout/upload_list_item.xml
new file mode 100755
index 00000000000..45e69d4be91
--- /dev/null
+++ b/res/layout/upload_list_item.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/upload_list_layout.xml b/res/layout/upload_list_layout.xml
new file mode 100755
index 00000000000..b691589eff2
--- /dev/null
+++ b/res/layout/upload_list_layout.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/menu/main_menu.xml b/res/menu/main_menu.xml
index 236bfd6e060..283aad05a68 100644
--- a/res/menu/main_menu.xml
+++ b/res/menu/main_menu.xml
@@ -49,6 +49,13 @@
android:title="@string/actionbar_sort"
android:contentDescription="@string/actionbar_sort"/>
+
+
diff --git a/res/menu/upload_actions_menu.xml b/res/menu/upload_actions_menu.xml
new file mode 100755
index 00000000000..e23804afc38
--- /dev/null
+++ b/res/menu/upload_actions_menu.xml
@@ -0,0 +1,10 @@
+
+
diff --git a/res/menu/upload_list_menu.xml b/res/menu/upload_list_menu.xml
new file mode 100755
index 00000000000..1a7e6d76585
--- /dev/null
+++ b/res/menu/upload_list_menu.xml
@@ -0,0 +1,31 @@
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cc3587d1ce0..c12528949a1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -75,6 +75,11 @@
Nothing in here. Upload something!
Loading…
There are no files in this folder.
+ No uploads available.
+ folder
+ folders
+ file
+ files
Tap on a file to display additional information.
Size:
Type:
@@ -91,6 +96,8 @@
OK
Cancel download
Cancel upload
+ Remove upload
+ Retry upload
Cancel
Save & Exit
Error
diff --git a/src/com/owncloud/android/MainApp.java b/src/com/owncloud/android/MainApp.java
index c805f9f7f4d..dc2deddf812 100644
--- a/src/com/owncloud/android/MainApp.java
+++ b/src/com/owncloud/android/MainApp.java
@@ -166,7 +166,9 @@ public static String getDBName() {
return getAppContext().getResources().getString(R.string.db_name);
}
- // data_folder
+ /**
+ * name of data_folder, e.g., "owncloud"
+ */
public static String getDataFolder() {
return getAppContext().getResources().getString(R.string.data_folder);
}
diff --git a/src/com/owncloud/android/authentication/AccountUtils.java b/src/com/owncloud/android/authentication/AccountUtils.java
index 87766b54614..93366890b0f 100644
--- a/src/com/owncloud/android/authentication/AccountUtils.java
+++ b/src/com/owncloud/android/authentication/AccountUtils.java
@@ -108,6 +108,22 @@ public static boolean exists(Account account, Context context) {
return false;
}
+ /**
+ * Returns owncloud account identified by accountName or null if it does not exist.
+ * @param context
+ * @param accountName name of account to be returned
+ * @return owncloud account named accountName
+ */
+ public static Account getOwnCloudAccountByName(Context context, String accountName) {
+ Account[] ocAccounts = AccountManager.get(context).getAccountsByType(
+ MainApp.getAccountType());
+ for (Account account : ocAccounts) {
+ if(account.name.equals(accountName))
+ return account;
+ }
+ return null;
+ }
+
public static boolean setCurrentOwnCloudAccount(Context context, String accountName) {
boolean result = false;
diff --git a/src/com/owncloud/android/datamodel/OCFile.java b/src/com/owncloud/android/datamodel/OCFile.java
index d1821b44e4f..a00ca8de9d7 100644
--- a/src/com/owncloud/android/datamodel/OCFile.java
+++ b/src/com/owncloud/android/datamodel/OCFile.java
@@ -20,16 +20,27 @@
package com.owncloud.android.datamodel;
+
+import java.io.File;
+import java.io.Serializable;
+
import android.os.Parcel;
import android.os.Parcelable;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.utils.FileStorageUtils;
-import java.io.File;
-
import third_parties.daveKoeller.AlphanumComparator;
-public class OCFile implements Parcelable, Comparable {
+
+// OCFile needs to be Serializable because it is stored persistently inside UploadDbObject.
+// (Parcelable is not suitable for persistent storage.)
+public class OCFile implements Parcelable, Comparable, Serializable {
+
+ /**
+ * Should be changed whenever any property of OCFile changes.
+ */
+ private static final long serialVersionUID = 5604080482686390078L;
+
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
@Override
@@ -204,7 +215,7 @@ public String getStoragePath() {
}
/**
- * Can be used to set the path where the file is stored
+ * Can be used to set the path where the local file is stored
*
* @param storage_path to set
*/
diff --git a/src/com/owncloud/android/db/DbHandler.java b/src/com/owncloud/android/db/DbHandler.java
deleted file mode 100644
index 661130666e1..00000000000
--- a/src/com/owncloud/android/db/DbHandler.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/**
- * ownCloud Android client application
- *
- * @author Bartek Przybylski
- * Copyright (C) 2011-2012 Bartek 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 .
- *
- */
-package com.owncloud.android.db;
-
-import com.owncloud.android.MainApp;
-import com.owncloud.android.lib.common.utils.Log_OC;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-
-/**
- * Custom database helper for ownCloud
- */
-public class DbHandler {
- private SQLiteDatabase mDB;
- private OpenerHelper mHelper;
- private final String mDatabaseName;
- private final int mDatabaseVersion = 3;
-
- private final String TABLE_INSTANT_UPLOAD = "instant_upload";
-
- public static final int UPLOAD_STATUS_UPLOAD_LATER = 0;
- public static final int UPLOAD_STATUS_UPLOAD_FAILED = 1;
-
- public DbHandler(Context context) {
- mDatabaseName = MainApp.getDBName();
- mHelper = new OpenerHelper(context);
- mDB = mHelper.getWritableDatabase();
- }
-
- public void close() {
- mDB.close();
- }
-
- public boolean putFileForLater(String filepath, String account, String message) {
- ContentValues cv = new ContentValues();
- cv.put("path", filepath);
- cv.put("account", account);
- cv.put("attempt", UPLOAD_STATUS_UPLOAD_LATER);
- cv.put("message", message);
- long result = mDB.insert(TABLE_INSTANT_UPLOAD, null, cv);
- Log_OC.d(TABLE_INSTANT_UPLOAD, "putFileForLater returns with: " + result + " for file: " + filepath);
- return result != -1;
- }
-
- public int updateFileState(String filepath, Integer status, String message) {
- ContentValues cv = new ContentValues();
- cv.put("attempt", status);
- cv.put("message", message);
- int result = mDB.update(TABLE_INSTANT_UPLOAD, cv, "path=?", new String[] { filepath });
- Log_OC.d(TABLE_INSTANT_UPLOAD, "updateFileState returns with: " + result + " for file: " + filepath);
- return result;
- }
-
- public Cursor getAwaitingFiles() {
- return mDB.query(TABLE_INSTANT_UPLOAD, null, "attempt=" + UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null);
- }
-
- public Cursor getFailedFiles() {
- return mDB.query(TABLE_INSTANT_UPLOAD, null, "attempt>" + UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null);
- }
-
- public void clearFiles() {
- mDB.delete(TABLE_INSTANT_UPLOAD, null, null);
- }
-
- /**
- *
- * @param localPath
- * @return true when one or more pending files was removed
- */
- public boolean removeIUPendingFile(String localPath) {
- long result = mDB.delete(TABLE_INSTANT_UPLOAD, "path = ?", new String[] { localPath });
- Log_OC.d(TABLE_INSTANT_UPLOAD, "delete returns with: " + result + " for file: " + localPath);
- return result != 0;
-
- }
-
- private class OpenerHelper extends SQLiteOpenHelper {
- public OpenerHelper(Context context) {
- super(context, mDatabaseName, null, mDatabaseVersion);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + TABLE_INSTANT_UPLOAD + " (" + " _id INTEGER PRIMARY KEY, " + " path TEXT,"
- + " account TEXT,attempt INTEGER,message TEXT);");
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (oldVersion < 2) {
- db.execSQL("ALTER TABLE " + TABLE_INSTANT_UPLOAD + " ADD COLUMN attempt INTEGER;");
- }
- db.execSQL("ALTER TABLE " + TABLE_INSTANT_UPLOAD + " ADD COLUMN message TEXT;");
- }
-
- @Override
- public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- //downgrading is the exception, so deleting and re-creating is acceptable.
- //otherwise exception will be thrown (cannot downgrade) and oc app will crash.
- db.execSQL("DROP TABLE IF EXISTS " + TABLE_INSTANT_UPLOAD + ";");
- onCreate(db);
- }
- }
-}
diff --git a/src/com/owncloud/android/db/ProviderMeta.java b/src/com/owncloud/android/db/ProviderMeta.java
index 25e8fbd1e56..d98bdb002ed 100644
--- a/src/com/owncloud/android/db/ProviderMeta.java
+++ b/src/com/owncloud/android/db/ProviderMeta.java
@@ -1,100 +1,103 @@
-/**
- * ownCloud Android client application
- *
- * @author Bartek Przybylski
- * Copyright (C) 2011 Bartek 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 .
- *
- */
-package com.owncloud.android.db;
-
-import android.net.Uri;
-import android.provider.BaseColumns;
-
-import com.owncloud.android.MainApp;
-
-/**
- * Meta-Class that holds various static field information
- */
-public class ProviderMeta {
-
- public static final String DB_NAME = "filelist";
- public static final int DB_VERSION = 10;
-
- private ProviderMeta() {
- }
-
- static public class ProviderTableMeta implements BaseColumns {
- public static final String FILE_TABLE_NAME = "filelist";
- public static final String OCSHARES_TABLE_NAME = "ocshares";
- public static final Uri CONTENT_URI = Uri.parse("content://"
- + MainApp.getAuthority() + "/");
- public static final Uri CONTENT_URI_FILE = Uri.parse("content://"
- + MainApp.getAuthority() + "/file");
- public static final Uri CONTENT_URI_DIR = Uri.parse("content://"
- + MainApp.getAuthority() + "/dir");
- public static final Uri CONTENT_URI_SHARE = Uri.parse("content://"
- + MainApp.getAuthority() + "/shares");
-
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.owncloud.file";
- public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.owncloud.file";
-
- // Columns of filelist table
- public static final String FILE_PARENT = "parent";
- public static final String FILE_NAME = "filename";
- public static final String FILE_CREATION = "created";
- public static final String FILE_MODIFIED = "modified";
- public static final String FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA = "modified_at_last_sync_for_data";
- public static final String FILE_CONTENT_LENGTH = "content_length";
- public static final String FILE_CONTENT_TYPE = "content_type";
- public static final String FILE_STORAGE_PATH = "media_path";
- public static final String FILE_PATH = "path";
- public static final String FILE_ACCOUNT_OWNER = "file_owner";
- public static final String FILE_LAST_SYNC_DATE = "last_sync_date"; // _for_properties, but let's keep it as it is
- public static final String FILE_LAST_SYNC_DATE_FOR_DATA = "last_sync_date_for_data";
- public static final String FILE_KEEP_IN_SYNC = "keep_in_sync";
- public static final String FILE_ETAG = "etag";
- public static final String FILE_SHARE_BY_LINK = "share_by_link";
- public static final String FILE_PUBLIC_LINK = "public_link";
- public static final String FILE_PERMISSIONS = "permissions";
- public static final String FILE_REMOTE_ID = "remote_id";
- public static final String FILE_UPDATE_THUMBNAIL = "update_thumbnail";
- public static final String FILE_IS_DOWNLOADING= "is_downloading";
-
- public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME
- + " collate nocase asc";
-
- // Columns of ocshares table
- public static final String OCSHARES_FILE_SOURCE = "file_source";
- public static final String OCSHARES_ITEM_SOURCE = "item_source";
- public static final String OCSHARES_SHARE_TYPE = "share_type";
- public static final String OCSHARES_SHARE_WITH = "shate_with";
- public static final String OCSHARES_PATH = "path";
- public static final String OCSHARES_PERMISSIONS = "permissions";
- public static final String OCSHARES_SHARED_DATE = "shared_date";
- public static final String OCSHARES_EXPIRATION_DATE = "expiration_date";
- public static final String OCSHARES_TOKEN = "token";
- public static final String OCSHARES_SHARE_WITH_DISPLAY_NAME = "shared_with_display_name";
- public static final String OCSHARES_IS_DIRECTORY = "is_directory";
- public static final String OCSHARES_USER_ID = "user_id";
- public static final String OCSHARES_ID_REMOTE_SHARED = "id_remote_shared";
- public static final String OCSHARES_ACCOUNT_OWNER = "owner_share";
-
- public static final String OCSHARES_DEFAULT_SORT_ORDER = OCSHARES_FILE_SOURCE
- + " collate nocase asc";
-
-
- }
-}
+/**
+ * ownCloud Android client application
+ *
+ * @author Bartek Przybylski
+ * Copyright (C) 2011 Bartek 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 .
+ *
+ */
+package com.owncloud.android.db;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+import com.owncloud.android.MainApp;
+
+/**
+ * Meta-Class that holds various static field information
+ */
+public class ProviderMeta {
+
+ public static final String DB_NAME = "filelist";
+ public static final int DB_VERSION = 10;
+
+ private ProviderMeta() {
+ }
+
+ static public class ProviderTableMeta implements BaseColumns {
+ public static final String FILE_TABLE_NAME = "filelist";
+ public static final String OCSHARES_TABLE_NAME = "ocshares";
+ public static final Uri CONTENT_URI = Uri.parse("content://"
+ + MainApp.getAuthority() + "/");
+ public static final Uri CONTENT_URI_FILE = Uri.parse("content://"
+ + MainApp.getAuthority() + "/file");
+ public static final Uri CONTENT_URI_DIR = Uri.parse("content://"
+ + MainApp.getAuthority() + "/dir");
+ public static final Uri CONTENT_URI_SHARE = Uri.parse("content://"
+ + MainApp.getAuthority() + "/shares");
+
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.owncloud.file";
+ public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.owncloud.file";
+
+ // Columns of filelist table
+ public static final String FILE_PARENT = "parent";
+ public static final String FILE_NAME = "filename";
+ public static final String FILE_CREATION = "created";
+ public static final String FILE_MODIFIED = "modified";
+ public static final String FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA =
+ "modified_at_last_sync_for_data";
+ public static final String FILE_CONTENT_LENGTH = "content_length";
+ public static final String FILE_CONTENT_TYPE = "content_type";
+ public static final String FILE_STORAGE_PATH = "media_path";
+ public static final String FILE_PATH = "path";
+ public static final String FILE_ACCOUNT_OWNER = "file_owner";
+ public static final String FILE_LAST_SYNC_DATE = "last_sync_date"; // _for_properties,
+ // but let's keep it as it is
+ public static final String FILE_LAST_SYNC_DATE_FOR_DATA = "last_sync_date_for_data";
+ public static final String FILE_KEEP_IN_SYNC = "keep_in_sync";
+ public static final String FILE_ETAG = "etag";
+ public static final String FILE_SHARE_BY_LINK = "share_by_link";
+ public static final String FILE_PUBLIC_LINK = "public_link";
+ public static final String FILE_PERMISSIONS = "permissions";
+ public static final String FILE_REMOTE_ID = "remote_id";
+ public static final String FILE_UPDATE_THUMBNAIL = "update_thumbnail";
+ public static final String FILE_IS_DOWNLOADING= "is_downloading";
+
+ public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME
+ + " collate nocase asc";
+
+ // Columns of ocshares table
+ public static final String OCSHARES_FILE_SOURCE = "file_source";
+ public static final String OCSHARES_ITEM_SOURCE = "item_source";
+ public static final String OCSHARES_SHARE_TYPE = "share_type";
+ public static final String OCSHARES_SHARE_WITH = "shate_with";
+ public static final String OCSHARES_PATH = "path";
+ public static final String OCSHARES_PERMISSIONS = "permissions";
+ public static final String OCSHARES_SHARED_DATE = "shared_date";
+ public static final String OCSHARES_EXPIRATION_DATE = "expiration_date";
+ public static final String OCSHARES_TOKEN = "token";
+ public static final String OCSHARES_SHARE_WITH_DISPLAY_NAME = "shared_with_display_name";
+ public static final String OCSHARES_IS_DIRECTORY = "is_directory";
+ public static final String OCSHARES_USER_ID = "user_id";
+ public static final String OCSHARES_ID_REMOTE_SHARED = "id_remote_shared";
+ public static final String OCSHARES_ACCOUNT_OWNER = "owner_share";
+
+ public static final String OCSHARES_DEFAULT_SORT_ORDER = OCSHARES_FILE_SOURCE
+ + " collate nocase asc";
+
+
+ }
+}
+
diff --git a/src/com/owncloud/android/db/UploadDbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java
new file mode 100644
index 00000000000..2283c925536
--- /dev/null
+++ b/src/com/owncloud/android/db/UploadDbHandler.java
@@ -0,0 +1,416 @@
+/* ownCloud Android client application
+ * Copyright (C) 2011-2012 Bartek Przybylski
+ * Copyright (C) 2012-2013 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 .
+ *
+ */
+package com.owncloud.android.db;
+
+import java.util.Observable;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import com.owncloud.android.MainApp;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.utils.Log_OC;
+
+/**
+ * Database helper for storing list of files to be uploaded, including status
+ * information for each file.
+ *
+ * @author Bartek Przybylski
+ * @author LukeOwncloud
+ *
+ */
+public class UploadDbHandler extends Observable {
+ private SQLiteDatabase mDB;
+ private OpenerHelper mHelper;
+ private final String mDatabaseName;
+ private final int mDatabaseVersion = 4;
+
+ static private final String TAG = "UploadDbHandler";
+ static private final String TABLE_UPLOAD = "list_of_uploads";
+
+ // for testing only
+ public void recreateDb() {
+// getDB().beginTransaction();
+// try {
+// mHelper.onUpgrade(getDB(), 0, mDatabaseVersion);
+// getDB().setTransactionSuccessful();
+// } finally {
+// getDB().endTransaction();
+// }
+ }
+
+ public enum UploadStatus {
+ /**
+ * Upload scheduled.
+ */
+ UPLOAD_LATER(0),
+ /**
+ * Last upload failed. Will retry.
+ */
+ UPLOAD_FAILED_RETRY(1),
+ /**
+ * Upload currently in progress.
+ */
+ UPLOAD_IN_PROGRESS(2),
+ /**
+ * Upload paused. Has to be manually resumed by user.
+ */
+ UPLOAD_PAUSED(3),
+ /**
+ * Upload was successful.
+ */
+ UPLOAD_SUCCEEDED(4),
+ /**
+ * Upload failed with some severe reason. Do not retry.
+ */
+ UPLOAD_FAILED_GIVE_UP(5),
+ /**
+ * User has cancelled upload. Do not retry.
+ */
+ UPLOAD_CANCELLED(6);
+ private final int value;
+
+ private UploadStatus(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ };
+
+ private UploadDbHandler(Context context) {
+ mDatabaseName = MainApp.getDBName();
+ mHelper = new OpenerHelper(context);
+ }
+
+ private static UploadDbHandler me = null;
+
+ static public UploadDbHandler getInstance(Context context) {
+ if (me == null) {
+ me = new UploadDbHandler(context);
+ }
+ return me;
+ }
+
+ public void close() {
+ getDB().close();
+ setDB(null);
+ me = null;
+ }
+
+ /**
+ * Store a file persistently (to be uploaded later).
+ *
+ * @param filepath local file path to file
+ * @param account account for uploading
+ * @param message optional message. can be null.
+ * @return false if an error occurred, else true.
+ */
+ public boolean storeFile(String filepath, String account, String message) {
+ // /OBSOLETE
+ Log_OC.i(TAG, "obsolete method called");
+ return false;
+ // ContentValues cv = new ContentValues();
+ // cv.put("path", filepath);
+ // cv.put("account", account);
+ // cv.put("attempt",
+ // UploadStatus.UPLOAD_STATUS_UPLOAD_LATER.getValue());
+ // cv.put("message", message);
+ // long result = mDB.insert(TABLE_UPLOAD, null, cv);
+ // Log_OC.d(TABLE_UPLOAD, "putFileForLater returns with: " + result +
+ // " for file: " + filepath);
+ // return result != -1;
+ }
+
+ // ununsed until now. uncomment if needed.
+ // public Cursor getFailedFiles() {
+ // return mDB.query(TABLE_INSTANT_UPLOAD, null, "attempt>" +
+ // UploadStatus.UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null);
+ // }
+
+ // ununsed until now. uncomment if needed.
+ // public void clearFiles() {
+ // mDB.delete(TABLE_INSTANT_UPLOAD, null, null);
+ // }
+
+ private class OpenerHelper extends SQLiteOpenHelper {
+ public OpenerHelper(Context context) {
+ super(context, mDatabaseName, null, mDatabaseVersion);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ // PRIMARY KEY should always imply NOT NULL. Unfortunately, due to a
+ // bug in some early versions, this is not the case in SQLite.
+ db.execSQL("CREATE TABLE " + TABLE_UPLOAD + " (" + " path TEXT PRIMARY KEY NOT NULL UNIQUE,"
+ + " uploadStatus INTEGER NOT NULL, uploadObject TEXT NOT NULL);");
+ // uploadStatus is used to easy filtering, it has precedence over
+ // uploadObject.getUploadStatus()
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (newVersion == 4) {
+ db.execSQL("DROP TABLE IF EXISTS " + "instant_upload" + ";"); // drop
+ // old
+ // db
+ // (name)
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_UPLOAD + ";");
+ onCreate(db);
+ }
+
+ }
+ }
+
+ /**
+ * Stores an upload object in DB.
+ *
+ * @param uploadObject
+ * @param message
+ * @return true on success.
+ */
+ public boolean storeUpload(UploadDbObject uploadObject) {
+ Log_OC.e(TAG, "Inserting "+uploadObject.getLocalPath()+" with uploadStatus="+uploadObject.getUploadStatus());
+
+ ContentValues cv = new ContentValues();
+ cv.put("path", uploadObject.getLocalPath());
+ cv.put("uploadStatus", uploadObject.getUploadStatus().value);
+ cv.put("uploadObject", uploadObject.toString());
+
+ long result = getDB().insert(TABLE_UPLOAD, null, cv);
+
+ Log_OC.d(TAG, "putFileForLater returns with: " + result + " for file: " + uploadObject.getLocalPath());
+ if (result == -1) {
+ Log_OC.e(TAG, "Failed to insert item " + uploadObject.getLocalPath() + " into upload db.");
+ return false;
+ } else {
+ notifyObserversNow();
+ return true;
+ }
+ }
+
+ /**
+ * Update upload status of file in DB.
+ *
+ * @return 1 if file status was updated, else 0.
+ */
+ public int updateUploadStatus(UploadDbObject uploadDbObject) {
+ return updateUploadStatus(uploadDbObject.getLocalPath(), uploadDbObject.getUploadStatus(),
+ uploadDbObject.getLastResult());
+ }
+
+ public int updateUploadInternal(Cursor c, UploadStatus status, RemoteOperationResult result) {
+
+ while(c.moveToNext()) {
+ // read upload object and update
+
+
+ String uploadObjectString = c.getString(c.getColumnIndex("uploadObject"));
+ UploadDbObject uploadObject = UploadDbObject.fromString(uploadObjectString);
+
+ String path = c.getString(c.getColumnIndex("path"));
+ Log_OC.v(
+ TAG,
+ "Updating " + path + " with status:" + status + " and result:"
+ + (result == null ? "null" : result.getCode()) + " (old:"
+ + uploadObject.toFormattedString() + ")");
+
+ uploadObject.setUploadStatus(status);
+ uploadObject.setLastResult(result);
+ uploadObjectString = uploadObject.toString();
+ // store update upload object to db
+ ContentValues cv = new ContentValues();
+ cv.put("uploadStatus", status.value);
+ cv.put("uploadObject", uploadObjectString);
+
+
+ int r = getDB().update(TABLE_UPLOAD, cv, "path=?", new String[] { path });
+
+ if (r == 1) {
+ notifyObserversNow();
+ } else {
+ Log_OC.e(TAG, "Failed to update upload db.");
+ }
+ return r;
+ }
+ return 0;
+ }
+ /**
+ * Update upload status of file uniquely referenced by filepath.
+ *
+ * @param filepath filepath local file path to file. used as identifier.
+ * @param status new status.
+ * @param result new result of upload operation
+ * @return 1 if file status was updated, else 0.
+ */
+ public int updateUploadStatus(String filepath, UploadStatus status, RemoteOperationResult result) {
+
+// Log_OC.e(TAG, "Updating "+filepath+" with uploadStatus="+status +" and result="+result);
+
+ Cursor c = getDB().query(TABLE_UPLOAD, null, "path=?", new String[] { filepath }, null, null, null);
+
+ if (c.getCount() != 1) {
+ Log_OC.e(TAG, c.getCount() + " items for path=" + filepath
+ + " available in UploadDb. Expected 1. Failed to update upload db.");
+ return 0;
+ }
+ return updateUploadInternal(c, status, result);
+ }
+
+ /**
+ * Should be called when some value of this DB was changed. All observers
+ * are informed.
+ */
+ public void notifyObserversNow() {
+ Log_OC.d("UploadListAdapter", "notifyObserversNow");
+ setChanged();
+ notifyObservers();
+ }
+
+ /**
+ * Remove upload from upload list. Should be called when cleaning up upload
+ * list.
+ *
+ * @param localPath
+ * @return true when one or more upload entries were removed
+ */
+ public int removeUpload(String localPath) {
+ int result = getDB().delete(TABLE_UPLOAD, "path = ?", new String[] { localPath });
+ Log_OC.d(TABLE_UPLOAD, "delete returns with: " + result + " for file: " + localPath);
+ if(result > 0) {
+ notifyObserversNow();
+ }
+ return result;
+ }
+
+ public UploadDbObject[] getAllStoredUploads() {
+ return getUploads(null, null);
+ }
+
+ public UploadDbObject[] getUploadByLocalPath(String localPath) {
+ return getUploads("path = ?", new String[] { localPath });
+ }
+
+
+ private UploadDbObject[] getUploads(String selection, String[] selectionArgs) {
+ Cursor c = getDB().query(TABLE_UPLOAD, null, selection, selectionArgs, null, null, null);
+ UploadDbObject[] list = new UploadDbObject[c.getCount()];
+ if (c.moveToFirst()) {
+ do {
+ String uploadObjectString = c.getString(c.getColumnIndex("uploadObject"));
+ UploadDbObject uploadObject = UploadDbObject.fromString(uploadObjectString);
+ if (uploadObject == null) {
+ Log_OC.e(TAG, "Could not deserialize UploadDbObject " + uploadObjectString);
+ } else {
+ list[c.getPosition()] = uploadObject;
+ }
+ } while (c.moveToNext());
+ }
+ return list;
+ }
+
+ /**
+ * Get all uploads which are pending, i.e., queued for upload but not
+ * currently being uploaded
+ *
+ * @return
+ */
+ public UploadDbObject[] getPendingUploads() {
+ return getUploads("uploadStatus==" + UploadStatus.UPLOAD_LATER.value + " OR uploadStatus=="
+ + UploadStatus.UPLOAD_FAILED_RETRY.value, null);
+ }
+
+ /**
+ * Get all uploads which are currently being uploaded. There should only be
+ * one. No guarantee though.
+ */
+ public UploadDbObject[] getCurrentUpload() {
+ return getUploads("uploadStatus==" + UploadStatus.UPLOAD_IN_PROGRESS.value, null);
+ }
+
+ /**
+ * Get all current and pending uploads.
+ */
+ public UploadDbObject[] getCurrentAndPendingUploads() {
+ return getUploads("uploadStatus==" + UploadStatus.UPLOAD_IN_PROGRESS.value + " OR uploadStatus=="
+ + UploadStatus.UPLOAD_LATER.value + " OR uploadStatus==" + UploadStatus.UPLOAD_FAILED_RETRY.value
+ + " OR uploadStatus==" + UploadStatus.UPLOAD_PAUSED.value, null);
+ }
+
+ /**
+ * Get all unrecoverably failed. Upload of these should/must/will not be
+ * retried.
+ */
+ public UploadDbObject[] getFailedUploads() {
+ return getUploads("uploadStatus==" + UploadStatus.UPLOAD_FAILED_GIVE_UP.value + " OR uploadStatus=="
+ + UploadStatus.UPLOAD_CANCELLED.value, null);
+ }
+
+ /**
+ * Get all uploads which where successfully completed.
+ */
+ public UploadDbObject[] getFinishedUploads() {
+ return getUploads("uploadStatus==" + UploadStatus.UPLOAD_SUCCEEDED.value, null);
+ }
+
+ private SQLiteDatabase getDB() {
+ if (mDB == null) {
+ mDB = mHelper.getWritableDatabase();
+ }
+ return mDB;
+ }
+
+ private void setDB(SQLiteDatabase mDB) {
+ this.mDB = mDB;
+ }
+
+ public long clearFailedUploads() {
+ String[] where = new String[2];
+ where[0] = String.valueOf(UploadStatus.UPLOAD_CANCELLED.value);
+ where[1] = String.valueOf(UploadStatus.UPLOAD_FAILED_GIVE_UP.value);
+ long result = getDB().delete(TABLE_UPLOAD, "uploadStatus = ? OR uploadStatus = ?", where);
+ Log_OC.d(TABLE_UPLOAD, "delete all failed uploads");
+ if (result > 0) {
+ notifyObserversNow();
+ }
+ return result;
+ }
+
+ public long clearFinishedUploads() {
+ String[] where = new String[1];
+ where[0] = String.valueOf(UploadStatus.UPLOAD_SUCCEEDED.value);
+ long result = getDB().delete(TABLE_UPLOAD, "uploadStatus = ?", where);
+ Log_OC.d(TABLE_UPLOAD, "delete all finished uploads");
+ if (result > 0) {
+ notifyObserversNow();
+ }
+ return result;
+ }
+
+ public void setAllCurrentToUploadLater() {
+
+ Cursor c = getDB().query(TABLE_UPLOAD, null, "uploadStatus==" + UploadStatus.UPLOAD_IN_PROGRESS.value, null, null, null, null);
+
+ updateUploadInternal(c, UploadStatus.UPLOAD_LATER, null);
+ }
+
+}
diff --git a/src/com/owncloud/android/db/UploadDbObject.java b/src/com/owncloud/android/db/UploadDbObject.java
new file mode 100755
index 00000000000..39f773830da
--- /dev/null
+++ b/src/com/owncloud/android/db/UploadDbObject.java
@@ -0,0 +1,321 @@
+package com.owncloud.android.db;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import android.accounts.Account;
+import android.content.Context;
+import android.util.Base64;
+
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.db.UploadDbHandler.UploadStatus;
+import com.owncloud.android.files.services.FileUploadService.LocalBehaviour;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.utils.Log_OC;
+
+/**
+ * Stores all information in order to start upload operations. PersistentUploadObject can
+ * be stored persistently by {@link UploadDbHandler}.
+ *
+ * @author LukeOwncloud
+ *
+ */
+public class UploadDbObject implements Serializable {
+
+ /** Generated - should be refreshed every time the class changes!! */
+ ;
+ private static final long serialVersionUID = -2306246191385279928L;
+
+ private static final String TAG = "UploadDbObject";
+
+ public UploadDbObject(OCFile ocFile) {
+ this.ocFile = ocFile;
+ }
+// /**
+// * Local path to file which is to be uploaded.
+// */
+// String localPath;
+// /**
+// * Remote path where file is to be uploaded to.
+// */
+// String remotePath;
+//
+// /**
+// * Mime type of upload file.
+// */
+// String mimeType;
+ OCFile ocFile;
+
+ public OCFile getOCFile() {
+ return ocFile;
+ }
+
+
+ /**
+ * Local action for upload.
+ */
+ LocalBehaviour localAction;
+
+ /**
+ * Date and time when this upload was first requested.
+ */
+ Calendar uploadTime = new GregorianCalendar();
+
+ public Calendar getUploadTime() {
+ return uploadTime;
+ }
+
+ /**
+ * @return the uploadStatus
+ */
+ public UploadStatus getUploadStatus() {
+ return uploadStatus;
+ }
+
+ /**
+ * Sets uploadStatus AND SETS lastResult = null;
+ * @param uploadStatus the uploadStatus to set
+ */
+ public void setUploadStatus(UploadStatus uploadStatus) {
+ this.uploadStatus = uploadStatus;
+ setLastResult(null);
+ }
+
+ /**
+ * @return the lastResult
+ */
+ public RemoteOperationResult getLastResult() {
+ return lastResult;
+ }
+
+ /**
+ * @param lastResult the lastResult to set
+ */
+ public void setLastResult(RemoteOperationResult lastResult) {
+ this.lastResult = lastResult;
+ }
+
+ /**
+ * Overwrite destination file?
+ */
+ boolean forceOverwrite;
+ /**
+ * Create destination folder?
+ */
+ boolean isCreateRemoteFolder;
+ /**
+ * Upload only via wifi?
+ */
+ boolean isUseWifiOnly;
+ /**
+ * Upload only if phone being charged?
+ */
+ boolean isWhileChargingOnly;
+ /**
+ * Earliest time when upload may be started. Negative if not set.
+ */
+ long uploadTimestamp;
+
+ /**
+ * Name of Owncloud account to upload file to.
+ */
+ String accountName;
+
+ /**
+ * Status of upload (later, in_progress, ...).
+ */
+ UploadStatus uploadStatus;
+
+ /**
+ * Result from last upload operation. Can be null.
+ */
+ RemoteOperationResult lastResult;
+
+ /**
+ * @return the localPath
+ */
+ public String getLocalPath() {
+ return ocFile.getStoragePath();
+ }
+
+ /**
+ * @return the remotePath
+ */
+ public String getRemotePath() {
+ return ocFile.getRemotePath();
+ }
+
+ /**
+ * @return the mimeType
+ */
+ public String getMimeType() {
+ return ocFile.getMimetype();
+ }
+
+
+ /**
+ * @return the localAction
+ */
+ public LocalBehaviour getLocalAction() {
+ // return null;
+ return localAction;
+ }
+
+ /**
+ * @param localAction the localAction to set
+ */
+ public void setLocalAction(LocalBehaviour localAction) {
+ this.localAction = localAction;
+ }
+
+ /**
+ * @return the forceOverwrite
+ */
+ public boolean isForceOverwrite() {
+ return forceOverwrite;
+ }
+
+ /**
+ * @param forceOverwrite the forceOverwrite to set
+ */
+ public void setForceOverwrite(boolean forceOverwrite) {
+ this.forceOverwrite = forceOverwrite;
+ }
+
+ /**
+ * @return the isCreateRemoteFolder
+ */
+ public boolean isCreateRemoteFolder() {
+ return isCreateRemoteFolder;
+ }
+
+ /**
+ * @param isCreateRemoteFolder the isCreateRemoteFolder to set
+ */
+ public void setCreateRemoteFolder(boolean isCreateRemoteFolder) {
+ this.isCreateRemoteFolder = isCreateRemoteFolder;
+ }
+
+ /**
+ * @return the isUseWifiOnly
+ */
+ public boolean isUseWifiOnly() {
+ return isUseWifiOnly;
+ }
+
+ /**
+ * @param isUseWifiOnly the isUseWifiOnly to set
+ */
+ public void setUseWifiOnly(boolean isUseWifiOnly) {
+ this.isUseWifiOnly = isUseWifiOnly;
+ }
+
+ /**
+ * @return the accountName
+ */
+ public String getAccountName() {
+ return accountName;
+ }
+
+ /**
+ * @param accountName the accountName to set
+ */
+ public void setAccountName(String accountName) {
+ this.accountName = accountName;
+ }
+
+ /**
+ * Returns a base64 encoded serialized string of this object.
+ */
+ @Override
+ public String toString() {
+ // serialize the object
+ try {
+ ByteArrayOutputStream bo = new ByteArrayOutputStream();
+ ObjectOutputStream so = new ObjectOutputStream(bo);
+ so.writeObject(this);
+ so.flush();
+ String serializedObjectBase64 = Base64.encodeToString(bo.toByteArray(), Base64.DEFAULT);
+ so.close();
+ bo.close();
+ return serializedObjectBase64;
+ } catch (Exception e) {
+ Log_OC.e(TAG, "Cannot serialize UploadDbObject with localPath:" + getLocalPath(), e);
+ }
+ return null;
+ }
+
+ /**
+ * Accepts a base64 encoded serialized string of an {@link UploadDbObject}
+ * and instantiates and returns an according object.
+ *
+ * @param serializedObjectBase64
+ * @return
+ */
+ static public UploadDbObject fromString(String serializedObjectBase64) {
+ // deserialize the object
+ try {
+ byte[] b = Base64.decode(serializedObjectBase64, Base64.DEFAULT);
+ ByteArrayInputStream bi = new ByteArrayInputStream(b);
+ ObjectInputStream si = new ObjectInputStream(bi);
+ UploadDbObject obj = (UploadDbObject) si.readObject();
+ return obj;
+ } catch (Exception e) {
+ Log_OC.e(TAG, "Cannot deserialize UploadDbObject " + serializedObjectBase64, e);
+ }
+ return null;
+ }
+
+ /**
+ * Returns owncloud account as {@link Account} object.
+ */
+ public Account getAccount(Context context) {
+ return AccountUtils.getOwnCloudAccountByName(context, getAccountName());
+ }
+
+ public void setWhileChargingOnly(boolean isWhileChargingOnly) {
+ this.isWhileChargingOnly = isWhileChargingOnly;
+ }
+
+ public boolean isWhileChargingOnly() {
+ return isWhileChargingOnly;
+ }
+
+ /**
+ * Earliest time when upload may be started. Negative if not set.
+ * @return the uploadTimestamp
+ */
+ public long getUploadTimestamp() {
+ return uploadTimestamp;
+ }
+
+ /**
+ * Earliest time when upload may be started. Set to negative value for immediate upload.
+ * @param uploadTimestamp the uploadTimestamp to set
+ */
+ public void setUploadTimestamp(long uploadTimestamp) {
+ this.uploadTimestamp = uploadTimestamp;
+ }
+
+ /**
+ * For debugging purposes only.
+ */
+ public String toFormattedString() {
+ return getLocalPath() + " status:"+getUploadStatus() + " result:" + (getLastResult()==null?"null":getLastResult().getCode());
+ }
+
+ /**
+ * Removes all uploads restrictions. After calling this function upload is performed immediately if requested.
+ */
+ public void removeAllUploadRestrictions() {
+ setUseWifiOnly(false);
+ setWhileChargingOnly(false);
+ setUploadTimestamp(0);
+ }
+}
diff --git a/src/com/owncloud/android/files/FileMenuFilter.java b/src/com/owncloud/android/files/FileMenuFilter.java
index 2c2754cc1a6..28be5182aa2 100644
--- a/src/com/owncloud/android/files/FileMenuFilter.java
+++ b/src/com/owncloud/android/files/FileMenuFilter.java
@@ -32,8 +32,8 @@
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.files.services.FileUploadService;
+import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder;
import com.owncloud.android.services.OperationsService;
import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
import com.owncloud.android.ui.activity.ComponentsGetter;
@@ -55,7 +55,7 @@ public class FileMenuFilter {
* @param targetFile {@link OCFile} target of the action to filter in the {@link Menu}.
* @param account ownCloud {@link Account} holding targetFile.
* @param cg Accessor to app components, needed to access the
- * {@link FileUploader} and {@link FileDownloader} services
+ * {@link FileUploadService} and {@link FileDownloader} services
* @param context Android {@link Context}, needed to access build setup resources.
*/
public FileMenuFilter(OCFile targetFile, Account account, ComponentsGetter cg, Context context) {
diff --git a/src/com/owncloud/android/files/FileOperationsHelper.java b/src/com/owncloud/android/files/FileOperationsHelper.java
index ddee205093b..34c94b6b322 100644
--- a/src/com/owncloud/android/files/FileOperationsHelper.java
+++ b/src/com/owncloud/android/files/FileOperationsHelper.java
@@ -33,9 +33,10 @@
import com.owncloud.android.R;
import com.owncloud.android.authentication.AccountUtils;
import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.db.UploadDbObject;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
-
+import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder;
+import com.owncloud.android.lib.common.accounts.AccountUtils.Constants;
import com.owncloud.android.lib.common.network.WebdavUtils;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
@@ -49,7 +50,7 @@
*/
public class FileOperationsHelper {
- private static final String TAG = FileOperationsHelper.class.getName();
+ private static final String TAG = FileOperationsHelper.class.getSimpleName();
private static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG";
@@ -69,7 +70,8 @@ public void openFile(OCFile file) {
String encodedStoragePath = WebdavUtils.encodePath(storagePath);
Intent intentForSavedMimeType = new Intent(Intent.ACTION_VIEW);
- intentForSavedMimeType.setDataAndType(Uri.parse("file://"+ encodedStoragePath), file.getMimetype());
+ intentForSavedMimeType.setDataAndType(Uri.parse("file://"+ encodedStoragePath),
+ file.getMimetype());
intentForSavedMimeType.setFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
);
@@ -81,18 +83,22 @@ public void openFile(OCFile file) {
);
if (guessedMimeType != null && !guessedMimeType.equals(file.getMimetype())) {
intentForGuessedMimeType = new Intent(Intent.ACTION_VIEW);
- intentForGuessedMimeType.setDataAndType(Uri.parse("file://"+ encodedStoragePath), guessedMimeType);
+ intentForGuessedMimeType.setDataAndType(Uri.parse("file://" +
+ encodedStoragePath), guessedMimeType);
intentForGuessedMimeType.setFlags(
- Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ Intent.FLAG_GRANT_READ_URI_PERMISSION |
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION
);
}
}
Intent chooserIntent;
if (intentForGuessedMimeType != null) {
- chooserIntent = Intent.createChooser(intentForGuessedMimeType, mFileActivity.getString(R.string.actionbar_open_with));
+ chooserIntent = Intent.createChooser(intentForGuessedMimeType,
+ mFileActivity.getString(R.string.actionbar_open_with));
} else {
- chooserIntent = Intent.createChooser(intentForSavedMimeType, mFileActivity.getString(R.string.actionbar_open_with));
+ chooserIntent = Intent.createChooser(intentForSavedMimeType,
+ mFileActivity.getString(R.string.actionbar_open_with));
}
mFileActivity.startActivity(chooserIntent);
@@ -110,7 +116,8 @@ public void shareFileWithLink(OCFile file) {
String link = "https://fake.url";
Intent intent = createShareWithLinkIntent(link);
String[] packagesToExclude = new String[] { mFileActivity.getPackageName() };
- DialogFragment chooserDialog = ShareLinkToDialog.newInstance(intent, packagesToExclude, file);
+ DialogFragment chooserDialog = ShareLinkToDialog.newInstance(intent,
+ packagesToExclude, file);
chooserDialog.show(mFileActivity.getSupportFragmentManager(), FTAG_CHOOSER_DIALOG);
} else {
@@ -120,7 +127,9 @@ public void shareFileWithLink(OCFile file) {
} else {
// Show a Message
Toast t = Toast.makeText(
- mFileActivity, mFileActivity.getString(R.string.share_link_no_support_share_api), Toast.LENGTH_LONG
+ mFileActivity,
+ mFileActivity.getString(R.string.share_link_no_support_share_api),
+ Toast.LENGTH_LONG
);
t.show();
}
@@ -180,7 +189,9 @@ public void unshareFileWithLink(OCFile file) {
} else {
// Show a Message
- Toast t = Toast.makeText(mFileActivity, mFileActivity.getString(R.string.share_link_no_support_share_api), Toast.LENGTH_LONG);
+ Toast t = Toast.makeText(mFileActivity,
+ mFileActivity.getString(R.string.share_link_no_support_share_api),
+ Toast.LENGTH_LONG);
t.show();
}
@@ -198,7 +209,8 @@ public void sendDownloadedFile(OCFile file) {
// Show dialog, without the own app
String[] packagesToExclude = new String[] { mFileActivity.getPackageName() };
- DialogFragment chooserDialog = ShareLinkToDialog.newInstance(sendIntent, packagesToExclude, file);
+ DialogFragment chooserDialog = ShareLinkToDialog.newInstance(sendIntent,
+ packagesToExclude, file);
chooserDialog.show(mFileActivity.getSupportFragmentManager(), FTAG_CHOOSER_DIALOG);
} else {
@@ -283,6 +295,33 @@ public void createFolder(String remotePath, boolean createFullPath) {
mFileActivity.showLoadingDialog();
}
+ /**
+ * Retry uploading a failed or cancelled upload with force.
+ */
+ public void retryUpload(UploadDbObject upload) {
+ Account account = mFileActivity.getAccount();
+ FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder();
+ if (uploaderBinder != null) {
+ upload.removeAllUploadRestrictions(); //only this object, upload DB stays untouched.
+ uploaderBinder.retry(account, upload);
+ } else {
+ Log_OC.w(TAG, "uploaderBinder not set. Cannot remove " + upload.getOCFile());
+ }
+ }
+
+ /**
+ * Remove upload from upload list.
+ */
+ public void removeUploadFromList(UploadDbObject upload) {
+ Account account = mFileActivity.getAccount();
+ FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder();
+ if (uploaderBinder != null) {
+ uploaderBinder.remove(account, upload.getOCFile());
+ } else {
+ Log_OC.w(TAG, "uploaderBinder not set. Cannot remove " + upload.getOCFile());
+ }
+ }
+
/**
* Cancel the transference in downloads (files/folders) and file uploads
* @param file OCFile
@@ -290,7 +329,8 @@ public void createFolder(String remotePath, boolean createFullPath) {
public void cancelTransference(OCFile file) {
Account account = mFileActivity.getAccount();
if (file.isFolder()) {
- OperationsService.OperationsServiceBinder opsBinder = mFileActivity.getOperationsServiceBinder();
+ OperationsService.OperationsServiceBinder opsBinder =
+ mFileActivity.getOperationsServiceBinder();
if (opsBinder != null) {
opsBinder.cancel(account, file);
}
@@ -299,19 +339,29 @@ public void cancelTransference(OCFile file) {
// for both files and folders
FileDownloaderBinder downloaderBinder = mFileActivity.getFileDownloaderBinder();
FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder();
- if (downloaderBinder != null && downloaderBinder.isDownloading(account, file)) {
- downloaderBinder.cancel(account, file);
+ if (downloaderBinder != null) {
+ if (downloaderBinder.isDownloading(account, file)) {
+ // Remove etag for parent, if file is a keep_in_sync
+ if (file.isFavorite()) {
+ OCFile parent = mFileActivity.getStorageManager().getFileById(file.getParentId());
+ parent.setEtag("");
+ mFileActivity.getStorageManager().saveFile(parent);
+ }
- // TODO - review why is this here, and solve in a better way
- // Remove etag for parent, if file is a favorite
- if (file.isFavorite()) {
- OCFile parent = mFileActivity.getStorageManager().getFileById(file.getParentId());
- parent.setEtag("");
- mFileActivity.getStorageManager().saveFile(parent);
+ downloaderBinder.cancel(account, file);
+ } else {
+ Log_OC.d(TAG, "Download for " + file + " not in progress. Cannot cancel " + file);
}
-
- } else if (uploaderBinder != null && uploaderBinder.isUploading(account, file)) {
- uploaderBinder.cancel(account, file);
+ }
+ if (uploaderBinder != null) {
+ if (uploaderBinder.isUploading(account, file)) {
+ uploaderBinder.cancel(account, file);
+ } else {
+ Log_OC.d(TAG, "Upload for " + file + " not in progress. Cannot cancel.");
+ }
+ }
+ if(downloaderBinder == null && uploaderBinder == null) {
+ Log_OC.w(TAG, "Neither downloaderBinder nor uploaderBinder set. Cannot cancel.");
}
}
@@ -347,7 +397,8 @@ public void setOpIdWaitingFor(long waitingForOpId) {
*/
public boolean isVersionWithForbiddenCharacters() {
if (mFileActivity.getAccount() != null) {
- OwnCloudVersion serverVersion = AccountUtils.getServerVersion(mFileActivity.getAccount());
+ OwnCloudVersion serverVersion =
+ AccountUtils.getServerVersion(mFileActivity.getAccount());
return (serverVersion != null && serverVersion.isVersionWithForbiddenCharacters());
}
return false;
diff --git a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java
index 47f7127b6c1..2a214c7cc08 100644
--- a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java
+++ b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java
@@ -20,27 +20,22 @@
package com.owncloud.android.files;
-import java.io.File;
-
-import com.owncloud.android.MainApp;
-import com.owncloud.android.authentication.AccountUtils;
-import com.owncloud.android.db.DbHandler;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.utils.FileStorageUtils;
-
+import java.util.Map.Entry;
import android.accounts.Account;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo.State;
import android.preference.PreferenceManager;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Video;
-import android.webkit.MimeTypeMap;
+
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.db.UploadDbHandler;
+import com.owncloud.android.files.services.FileUploadService;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.utils.FileStorageUtils;
public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
@@ -58,22 +53,26 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log_OC.d(TAG, "Received: " + intent.getAction());
- if (intent.getAction().equals(android.net.ConnectivityManager.CONNECTIVITY_ACTION)) {
- handleConnectivityAction(context, intent);
- }else if (intent.getAction().equals(NEW_PHOTO_ACTION_UNOFFICIAL)) {
+ if (intent.getAction().equals(NEW_PHOTO_ACTION_UNOFFICIAL)) {
handleNewPictureAction(context, intent);
Log_OC.d(TAG, "UNOFFICIAL processed: com.android.camera.NEW_PICTURE");
} else if (intent.getAction().equals(NEW_PHOTO_ACTION)) {
handleNewPictureAction(context, intent);
Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_PICTURE");
} else if (intent.getAction().equals(NEW_VIDEO_ACTION)) {
- Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_VIDEO");
handleNewVideoAction(context, intent);
+ Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_VIDEO");
} else {
Log_OC.e(TAG, "Incorrect intent sent: " + intent.getAction());
}
}
+ /**
+ * Because we support NEW_PHOTO_ACTION and NEW_PHOTO_ACTION_UNOFFICIAL it can happen that
+ * handleNewPictureAction is called twice for the same photo. Use this simple static variable to
+ * remember last uploaded photo to filter duplicates. Must not be null!
+ */
+ static String lastUploadedPhotoPath = "";
private void handleNewPictureAction(Context context, Intent intent) {
Cursor c = null;
String file_path = null;
@@ -104,24 +103,21 @@ private void handleNewPictureAction(Context context, Intent intent) {
mime_type = c.getString(c.getColumnIndex(Images.Media.MIME_TYPE));
c.close();
- Log_OC.d(TAG, file_path + "");
-
- // save always temporally the picture to upload
- DbHandler db = new DbHandler(context);
- db.putFileForLater(file_path, account.name, null);
- db.close();
-
- if (!isOnline(context) || (instantPictureUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context))) {
+ if(file_path.equals(lastUploadedPhotoPath)) {
+ Log_OC.d(TAG, "Duplicate detected: " + file_path + ". Ignore.");
return;
}
-
- Intent i = new Intent(context, FileUploader.class);
- i.putExtra(FileUploader.KEY_ACCOUNT, account);
- i.putExtra(FileUploader.KEY_LOCAL_FILE, file_path);
- i.putExtra(FileUploader.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, file_name));
- i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
- i.putExtra(FileUploader.KEY_MIME_TYPE, mime_type);
- i.putExtra(FileUploader.KEY_INSTANT_UPLOAD, true);
+ lastUploadedPhotoPath = file_path;
+ Log_OC.d(TAG, "Path: " + file_path + "");
+
+ Intent i = new Intent(context, FileUploadService.class);
+ i.putExtra(FileUploadService.KEY_ACCOUNT, account);
+ i.putExtra(FileUploadService.KEY_LOCAL_FILE, file_path);
+ i.putExtra(FileUploadService.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, file_name));
+ i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UploadSingleMulti.UPLOAD_SINGLE_FILE);
+ i.putExtra(FileUploadService.KEY_MIME_TYPE, mime_type);
+ i.putExtra(FileUploadService.KEY_CREATE_REMOTE_FOLDER, true);
+ i.putExtra(FileUploadService.KEY_WIFI_ONLY, instantPictureUploadViaWiFiOnly(context));
context.startService(i);
}
@@ -156,81 +152,67 @@ private void handleNewVideoAction(Context context, Intent intent) {
c.close();
Log_OC.d(TAG, file_path + "");
- if (!isOnline(context) || (instantVideoUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context))) {
- return;
- }
-
- Intent i = new Intent(context, FileUploader.class);
- i.putExtra(FileUploader.KEY_ACCOUNT, account);
- i.putExtra(FileUploader.KEY_LOCAL_FILE, file_path);
- i.putExtra(FileUploader.KEY_REMOTE_FILE, FileStorageUtils.getInstantVideoUploadFilePath(context, file_name));
- i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
- i.putExtra(FileUploader.KEY_MIME_TYPE, mime_type);
- i.putExtra(FileUploader.KEY_INSTANT_UPLOAD, true);
+ Intent i = new Intent(context, FileUploadService.class);
+ i.putExtra(FileUploadService.KEY_ACCOUNT, account);
+ i.putExtra(FileUploadService.KEY_LOCAL_FILE, file_path);
+ i.putExtra(FileUploadService.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, file_name));
+ i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UploadSingleMulti.UPLOAD_SINGLE_FILE);
+ i.putExtra(FileUploadService.KEY_MIME_TYPE, mime_type);
+ i.putExtra(FileUploadService.KEY_CREATE_REMOTE_FOLDER, true);
+ i.putExtra(FileUploadService.KEY_WIFI_ONLY, instantVideoUploadViaWiFiOnly(context));
context.startService(i);
-
- }
-
- private void handleConnectivityAction(Context context, Intent intent) {
- if (!instantPictureUploadEnabled(context)) {
- Log_OC.d(TAG, "Instant upload disabled, don't upload anything");
- return;
- }
-
- if (!intent.hasExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY)
- && isOnline(context)
- && (!instantPictureUploadViaWiFiOnly(context) || (instantPictureUploadViaWiFiOnly(context) == isConnectedViaWiFi(context) == true))) {
- DbHandler db = new DbHandler(context);
- Cursor c = db.getAwaitingFiles();
- if (c.moveToFirst()) {
- do {
- String account_name = c.getString(c.getColumnIndex("account"));
- String file_path = c.getString(c.getColumnIndex("path"));
- File f = new File(file_path);
- if (f.exists()) {
- Account account = new Account(account_name, MainApp.getAccountType());
-
- String mimeType = null;
- try {
- mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
- f.getName().substring(f.getName().lastIndexOf('.') + 1));
-
- } catch (Throwable e) {
- Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + f.getName());
- }
- if (mimeType == null)
- mimeType = "application/octet-stream";
-
- Intent i = new Intent(context, FileUploader.class);
- i.putExtra(FileUploader.KEY_ACCOUNT, account);
- i.putExtra(FileUploader.KEY_LOCAL_FILE, file_path);
- i.putExtra(FileUploader.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, f.getName()));
- i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
- i.putExtra(FileUploader.KEY_INSTANT_UPLOAD, true);
- context.startService(i);
-
- } else {
- Log_OC.w(TAG, "Instant upload file " + f.getAbsolutePath() + " dont exist anymore");
- }
- } while (c.moveToNext());
- }
- c.close();
- db.close();
- }
-
}
- public static boolean isOnline(Context context) {
- ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected();
- }
-
- public static boolean isConnectedViaWiFi(Context context) {
- ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- return cm != null && cm.getActiveNetworkInfo() != null
- && cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI
- && cm.getActiveNetworkInfo().getState() == State.CONNECTED;
- }
+ //obsolete. delete.
+// private void handleConnectivityAction(Context context, Intent intent) {
+// if (!instantPictureUploadEnabled(context)) {
+// Log_OC.d(TAG, "Instant upload disabled, don't upload anything");
+// return;
+// }
+//
+// if (!intent.hasExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY)
+// && isOnline(context)
+// && (!instantPictureUploadViaWiFiOnly(context) || (instantPictureUploadViaWiFiOnly(context) == isConnectedViaWiFi(context) == true))) {
+// UploadDbHandler db = new UploadDbHandler(context);
+// Cursor c = db.getAwaitingFiles();
+// if (c.moveToFirst()) {
+// do {
+// String account_name = c.getString(c.getColumnIndex("account"));
+// String file_path = c.getString(c.getColumnIndex("path"));
+// File f = new File(file_path);
+// if (f.exists()) {
+// Account account = new Account(account_name, MainApp.getAccountType());
+//
+// String mimeType = null;
+// try {
+// mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+// f.getName().substring(f.getName().lastIndexOf('.') + 1));
+//
+// } catch (Throwable e) {
+// Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + f.getName());
+// }
+// if (mimeType == null)
+// mimeType = "application/octet-stream";
+//
+// Intent i = new Intent(context, FileUploader.class);
+// i.putExtra(FileUploader.KEY_ACCOUNT, account);
+// i.putExtra(FileUploader.KEY_LOCAL_FILE, file_path);
+// i.putExtra(FileUploader.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, f.getName()));
+// i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
+// i.putExtra(FileUploader.KEY_CREATE_REMOTE_FOLDER, true);
+// i.putExtra(FileUploader.KEY_WIFI_ONLY, instantPictureUploadViaWiFiOnly(context));
+// context.startService(i);
+//
+// } else {
+// Log_OC.w(TAG, "Instant upload file " + f.getAbsolutePath() + " dont exist anymore");
+// }
+// } while (c.moveToNext());
+// }
+// c.close();
+// db.close();
+// }
+//
+// }
public static boolean instantPictureUploadEnabled(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_uploading", false);
diff --git a/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java b/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java
new file mode 100755
index 00000000000..23ac7a527ed
--- /dev/null
+++ b/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java
@@ -0,0 +1,95 @@
+package com.owncloud.android.files.services;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.State;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.owncloud.android.files.InstantUploadBroadcastReceiver;
+import com.owncloud.android.lib.common.utils.Log_OC;
+
+/**
+ * Receives all connectivity action from Android OS at all times and performs
+ * required OC actions. For now that are: - Signal connectivity to
+ * {@link FileUploadService}.
+ *
+ * Later can be added: - Signal connectivity to download service, deletion
+ * service, ... - Handle offline mode (cf.
+ * https://github.com/owncloud/android/issues/162)
+ *
+ * @author LukeOwncloud
+ *
+ */
+public class ConnectivityActionReceiver extends BroadcastReceiver {
+ private static final String TAG = "ConnectivityActionReceiver";
+
+ @Override
+ public void onReceive(final Context context, Intent intent) {
+ // LOG ALL EVENTS:
+ Log.v(TAG, "action: " + intent.getAction());
+ Log.v(TAG, "component: " + intent.getComponent());
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ for (String key : extras.keySet()) {
+ Log.v(TAG, "key [" + key + "]: " + extras.get(key));
+ }
+ } else {
+ Log.v(TAG, "no extras");
+ }
+
+
+ /**
+ * Just checking for State.CONNECTED will is not good enough, as it ends here multiple times.
+ * Work around from:
+ * http://stackoverflow.com/questions/17287178/connectivitymanager-getactivenetworkinfo-returning-true-when-internet-is-off
+ */
+ if(intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ NetworkInfo networkInfo =
+ intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+ if(networkInfo.isConnected()) {
+ Log.d(TAG, "Wifi is connected: " + String.valueOf(networkInfo));
+ wifiConnected(context);
+ }
+ } else if(intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ ConnectivityManager cm =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = cm.getActiveNetworkInfo();
+ if(networkInfo == null || networkInfo.getType() == ConnectivityManager.TYPE_WIFI &&
+ ! networkInfo.isConnected()) {
+ Log.d(TAG, "Wifi is disconnected: " + String.valueOf(networkInfo));
+ wifiDisconnected(context);
+ }
+ }
+
+ }
+
+ private void wifiConnected(Context context) {
+ Log_OC.d(TAG, "FileUploadService.retry() called by onReceive()");
+ FileUploadService.retry(context);
+ }
+
+ private void wifiDisconnected(Context context) {
+
+ }
+
+ static public void enableActionReceiver(Context context) {
+ PackageManager pm = context.getPackageManager();
+ ComponentName compName = new ComponentName(context.getApplicationContext(), ConnectivityActionReceiver.class);
+ pm.setComponentEnabledSetting(compName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ static public void disableActionReceiver(Context context) {
+ PackageManager pm = context.getPackageManager();
+ ComponentName compName = new ComponentName(context.getApplicationContext(), ConnectivityActionReceiver.class);
+ pm.setComponentEnabledSetting(compName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+}
\ No newline at end of file
diff --git a/src/com/owncloud/android/files/services/FileDownloader.java b/src/com/owncloud/android/files/services/FileDownloader.java
index f6d3b50f49a..2af609138a0 100644
--- a/src/com/owncloud/android/files/services/FileDownloader.java
+++ b/src/com/owncloud/android/files/services/FileDownloader.java
@@ -34,7 +34,6 @@
import com.owncloud.android.authentication.AuthenticatorActivity;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
-
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
@@ -66,6 +65,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcelable;
import android.os.Process;
import android.support.v4.app.NotificationCompat;
import android.util.Pair;
@@ -398,7 +398,6 @@ public void checkAccountOfCurrentDownload() {
}
-
/**
* Download worker. Performs the pending downloads in the order they were requested.
*
@@ -569,7 +568,7 @@ private void notifyDownloadStart(DownloadFileOperation download) {
} else {
showDetailsIntent = new Intent(this, FileDisplayActivity.class);
}
- showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, download.getFile());
+ showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable)download.getFile());
showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, download.getAccount());
showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java
new file mode 100644
index 00000000000..f55c56168d8
--- /dev/null
+++ b/src/com/owncloud/android/files/services/FileUploadService.java
@@ -0,0 +1,1395 @@
+/* ownCloud Android client application
+ * Copyright (C) 2012 Bartek Przybylski
+ * Copyright (C) 2012-2013 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 .
+ *
+ */
+
+package com.owncloud.android.files.services;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountsException;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcelable;
+import android.support.v4.app.NotificationCompat;
+import android.webkit.MimeTypeMap;
+
+import com.owncloud.android.R;
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.authentication.AuthenticatorActivity;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.db.UploadDbHandler;
+import com.owncloud.android.db.UploadDbHandler.UploadStatus;
+import com.owncloud.android.db.UploadDbObject;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
+import com.owncloud.android.lib.common.accounts.AccountUtils.Constants;
+import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
+import com.owncloud.android.lib.common.operations.OperationCancelledException;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
+import com.owncloud.android.lib.resources.files.FileUtils;
+import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
+import com.owncloud.android.lib.resources.files.RemoteFile;
+import com.owncloud.android.lib.resources.status.OwnCloudVersion;
+import com.owncloud.android.notifications.NotificationBuilderWithProgressBar;
+import com.owncloud.android.notifications.NotificationDelayer;
+import com.owncloud.android.operations.CreateFolderOperation;
+import com.owncloud.android.operations.UploadFileOperation;
+import com.owncloud.android.operations.common.SyncOperation;
+import com.owncloud.android.ui.activity.FileActivity;
+import com.owncloud.android.ui.activity.FileDisplayActivity;
+import com.owncloud.android.ui.activity.UploadListActivity;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.ErrorMessageAdapter;
+import com.owncloud.android.utils.UploadUtils;
+import com.owncloud.android.utils.UriUtils;
+
+/**
+ * Service for uploading files. Invoke using context.startService(...). Files to
+ * be uploaded are stored persistently using {@link UploadDbHandler}.
+ *
+ * On next invocation of {@link FileUploadService} uploaded files which
+ * previously failed will be uploaded again until either upload succeeded or a
+ * fatal error occured.
+ *
+ * Every file passed to this service is uploaded. No filtering is performed.
+ * However, Intent keys (e.g., KEY_WIFI_ONLY) are obeyed.
+ *
+ * @author LukeOwncloud
+ *
+ */
+@SuppressWarnings("unused")
+public class FileUploadService extends Service implements OnDatatransferProgressListener {
+
+ private volatile Looper mServiceLooper;
+ private volatile ServiceHandler mServiceHandler;
+ private ExecutorService uploadExecutor;
+
+ public FileUploadService() {
+ super();
+ }
+
+ private static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH";
+ public static final String EXTRA_UPLOAD_RESULT = "RESULT";
+ public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
+ public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH";
+ public static final String EXTRA_OLD_FILE_PATH = "OLD_FILE_PATH";
+ public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
+
+ public static final String KEY_FILE = "FILE";
+ public static final String KEY_LOCAL_FILE = "LOCAL_FILE";
+ public static final String KEY_REMOTE_FILE = "REMOTE_FILE";
+ public static final String KEY_MIME_TYPE = "MIME_TYPE";
+
+ /**
+ * Call this Service with only this Intent key if all pending uploads are to be retried.
+ */
+ private static final String KEY_RETRY = "KEY_RETRY";
+ /**
+ * Call this Service with KEY_RETRY and KEY_RETRY_REMOTE_PATH to retry
+ * download of file identified by KEY_RETRY_REMOTE_PATH.
+ */
+ private static final String KEY_RETRY_REMOTE_PATH = "KEY_RETRY_REMOTE_PATH";
+ /**
+ * {@link Account} to which file is to be uploaded.
+ */
+ public static final String KEY_ACCOUNT = "ACCOUNT";
+ /**
+ * Set whether single file or multiple files are to be uploaded. Value must be of type {@link UploadSingleMulti}.
+ */
+ public static final String KEY_UPLOAD_TYPE = "UPLOAD_TYPE";
+ /**
+ * Set to true if remote file is to be overwritten. Default action is to upload with different name.
+ */
+ public static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE";
+ /**
+ * Set to true if remote folder is to be created if it does not exist.
+ */
+ public static final String KEY_CREATE_REMOTE_FOLDER = "CREATE_REMOTE_FOLDER";
+ /**
+ * Set to true if upload is to performed only when connected via wifi.
+ */
+ public static final String KEY_WIFI_ONLY = "WIFI_ONLY";
+ /**
+ * Set to true if upload is to performed only when phone is being charged.
+ */
+ public static final String KEY_WHILE_CHARGING_ONLY = "KEY_WHILE_CHARGING_ONLY";
+ /**
+ * Set to future UNIX timestamp. Upload will not be performed before this timestamp.
+ */
+ public static final String KEY_UPLOAD_TIMESTAMP= "KEY_UPLOAD_TIMESTAMP";
+
+ public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR";
+
+ /**
+ * Describes local behavior for upload.
+ */
+ public enum LocalBehaviour {
+ /**
+ * Creates a copy of file and stores it in tmp folder inside owncloud
+ * folder on sd-card. After upload it is moved to local owncloud
+ * storage. Original file stays untouched.
+ */
+ LOCAL_BEHAVIOUR_COPY(0),
+ /**
+ * Upload file from current storage. Afterwards original file is move to
+ * local owncloud storage.
+ */
+ LOCAL_BEHAVIOUR_MOVE(1),
+ /**
+ * Just uploads file and leaves it where it is. Original file stays
+ * untouched.
+ */
+ LOCAL_BEHAVIOUR_FORGET(2);
+ private final int value;
+
+ private LocalBehaviour(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ }
+
+ public enum UploadSingleMulti {
+ UPLOAD_SINGLE_FILE(0), UPLOAD_MULTIPLE_FILES(1);
+ private final int value;
+
+ private UploadSingleMulti(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ };
+
+ private static final String TAG = FileUploadService.class.getSimpleName();
+
+ private IBinder mBinder;
+ private OwnCloudClient mUploadClient = null;
+ private Account mLastAccount = null;
+ private FileDataStorageManager mStorageManager;
+ //since there can be only one instance of an Android service, there also just one db connection.
+ private UploadDbHandler mDb = null;
+
+ private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
+ private final AtomicBoolean mCancellationPossible = new AtomicBoolean(false);
+
+ /**
+ * List of uploads that are currently pending. Maps from remotePath to where file
+ * is being uploaded to {@link UploadFileOperation}.
+ */
+ private ConcurrentMap mPendingUploads = new ConcurrentHashMap();
+
+ /**
+ * {@link UploadFileOperation} object of ongoing upload. Can be null. Note: There can only be one concurrent upload!
+ */
+ private UploadFileOperation mCurrentUpload = null;
+
+ private NotificationManager mNotificationManager;
+ private NotificationCompat.Builder mNotificationBuilder;
+ private int mLastPercent;
+
+ private static final String MIME_TYPE_PDF = "application/pdf";
+ private static final String FILE_EXTENSION_PDF = ".pdf";
+
+ public static String getUploadFinishMessage() {
+ return FileUploadService.class.getName().toString() + UPLOAD_FINISH_MESSAGE;
+ }
+
+ /**
+ * Builds a key for mPendingUploads from the account and file to upload
+ *
+ * @param account Account where the file to upload is stored
+ * @param file File to upload
+ */
+ private String buildRemoteName(Account account, OCFile file) {
+ return account.name + file.getRemotePath();
+ }
+
+
+ private String buildRemoteName(Account account, String remotePath) {
+ return account.name + remotePath;
+ }
+
+ private String buildRemoteName(UploadDbObject uploadDbObject) {
+ return uploadDbObject.getAccountName() + uploadDbObject.getRemotePath();
+ }
+
+ /**
+ * Checks if an ownCloud server version should support chunked uploads.
+ *
+ * @param version OwnCloud version instance corresponding to an ownCloud
+ * server.
+ * @return 'True' if the ownCloud server with version supports chunked
+ * uploads.
+ */
+ private static boolean chunkedUploadIsSupported(OwnCloudVersion version) {
+ return (version != null && version.compareTo(OwnCloudVersion.owncloud_v4_5) >= 0);
+ }
+
+ /**
+ * Service initialization
+ */
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log_OC.d(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - onCreate");
+ mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ mBinder = new FileUploaderBinder();
+ mDb = UploadDbHandler.getInstance(this.getBaseContext());
+ mDb.recreateDb(); //for testing only
+
+ //when this service starts there is no upload in progress. if db says so, app probably crashed before.
+ mDb.setAllCurrentToUploadLater();
+
+ HandlerThread thread = new HandlerThread("FileUploadService-Requester");
+ thread.start();
+
+ mServiceLooper = thread.getLooper();
+ mServiceHandler = new ServiceHandler(mServiceLooper);
+
+ uploadExecutor = Executors.newFixedThreadPool(1);
+
+ Log_OC.d(TAG, "FileUploadService.retry() called by onCreate()");
+ FileUploadService.retry(getApplicationContext());
+ }
+
+ @Override
+ public void onDestroy() {
+ Log_OC.d(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - onDestroy");
+ mServiceLooper.quit();
+ uploadExecutor.shutdown();
+ }
+
+ private final class ServiceHandler extends Handler {
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ onHandleIntent((Intent) msg.obj, (int)msg.arg1);
+ }
+ }
+
+ @Override
+ public void onStart(Intent intent, int intentStartId) {
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg1 = intentStartId;
+ msg.obj = intent;
+ mServiceHandler.sendMessage(msg);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ onStart(intent, startId);
+ return START_STICKY; // if service is killed by OS before calling
+ // stopSelf(), tell OS to restart service with
+ // null-intent.
+ }
+
+ /**
+ * The IntentService calls this method from the default worker thread with
+ * the intent that started the service. When this method returns,
+ * IntentService stops the service, as appropriate.
+ *
+ * Note: We use an IntentService here. It does not provide simultaneous
+ * requests, but instead internally queues them and gets them to this
+ * onHandleIntent method one after another. This makes implementation less
+ * error-prone but prevents files to be added to list while another upload
+ * is active. If everything else works fine, fixing this should be a TODO.
+ *
+ * Entry point to add one or several files to the queue of uploads.
+ *
+ * New uploads are added calling to startService(), resulting in a call to
+ * this method. This ensures the service will keep on working although the
+ * caller activity goes away.
+ *
+ * First, onHandleIntent() stores all information associated with the upload
+ * in a {@link UploadDbObject} which is stored persistently using
+ * {@link UploadDbHandler}. Then, the oldest, pending upload from
+ * {@link UploadDbHandler} is taken and upload is started.
+ * @param intentStartId
+ */
+
+ protected void onHandleIntent(Intent intent, int intentStartId) {
+ Log_OC.d(TAG, "onHandleIntent start");
+ Log_OC.d(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - before adding new uploads.");
+ if (intent == null || intent.hasExtra(KEY_RETRY)) {
+ Log_OC.d(TAG, "Received null intent.");
+ // service was restarted by OS or connectivity change was detected or
+ // retry of pending upload was requested.
+ // ==> First check persistent uploads, then perform upload.
+ int countAddedEntries = 0;
+ UploadDbObject[] list = mDb.getPendingUploads();
+ for (UploadDbObject uploadDbObject : list) {
+ Log_OC.d(TAG, "Retrieved from DB: " + uploadDbObject.toFormattedString());
+
+ String uploadKey = buildRemoteName(uploadDbObject);
+ UploadDbObject previous = mPendingUploads.putIfAbsent(uploadKey, uploadDbObject);
+ if(previous == null) {
+ Log_OC.d(TAG, "mPendingUploads added: " + uploadDbObject.toFormattedString());
+ countAddedEntries++;
+ } else {
+ //already pending. ignore.
+ }
+ }
+ Log_OC.d(TAG, "added " + countAddedEntries
+ + " entrie(s) to mPendingUploads (this should be 0 except for the first time).");
+ // null intent is received when charging or wifi state changes.
+ // fake a mDb change event, so that GUI can update the reason for
+ // LATER status of uploads.
+ mDb.notifyObserversNow();
+ } else {
+ Log_OC.d(TAG, "Receive upload intent.");
+ UploadSingleMulti uploadType = (UploadSingleMulti) intent.getSerializableExtra(KEY_UPLOAD_TYPE);
+ if (uploadType == null) {
+ Log_OC.e(TAG, "Incorrect or no upload type provided");
+ return;
+ }
+
+ Account account = intent.getParcelableExtra(KEY_ACCOUNT);
+ if (!AccountUtils.exists(account, getApplicationContext())) {
+ Log_OC.e(TAG, "KEY_ACCOUNT no set or provided KEY_ACCOUNT does not exist");
+ return;
+ }
+
+ OCFile[] files = null;
+ // if KEY_FILE given, use it
+ if (intent.hasExtra(KEY_FILE)) {
+ if (uploadType == UploadSingleMulti.UPLOAD_SINGLE_FILE) {
+ files = new OCFile[] { intent.getParcelableExtra(KEY_FILE) };
+ } else {
+ files = (OCFile[]) intent.getParcelableArrayExtra(KEY_FILE);
+ }
+
+ } else { // else use KEY_LOCAL_FILE and KEY_REMOTE_FILE
+
+ if (!intent.hasExtra(KEY_LOCAL_FILE) || !intent.hasExtra(KEY_REMOTE_FILE)) {
+ Log_OC.e(TAG, "Not enough information provided in intent");
+ return;
+ }
+
+ String[] localPaths;
+ String[] remotePaths;
+ String[] mimeTypes;
+ if (uploadType == UploadSingleMulti.UPLOAD_SINGLE_FILE) {
+ localPaths = new String[] { intent.getStringExtra(KEY_LOCAL_FILE) };
+ remotePaths = new String[] { intent.getStringExtra(KEY_REMOTE_FILE) };
+ mimeTypes = new String[] { intent.getStringExtra(KEY_MIME_TYPE) };
+ } else {
+ localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE);
+ remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE);
+ mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE);
+ }
+ if (localPaths.length != remotePaths.length) {
+ Log_OC.e(TAG, "Different number of remote paths and local paths!");
+ return;
+ }
+
+ files = new OCFile[localPaths.length];
+
+ for (int i = 0; i < localPaths.length; i++) {
+ files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i],
+ ((mimeTypes != null) ? mimeTypes[i] : (String) null));
+ if (files[i] == null) {
+ Log_OC.e(TAG, "obtainNewOCFileToUpload() returned null for remotePaths[i]:" + remotePaths[i]
+ + " and localPaths[i]:" + localPaths[i]);
+ return;
+ }
+ }
+ }
+
+ // at this point variable "OCFile[] files" is loaded correctly.
+
+ boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false);
+ boolean isCreateRemoteFolder = intent.getBooleanExtra(KEY_CREATE_REMOTE_FOLDER, false);
+ boolean isUseWifiOnly = intent.getBooleanExtra(KEY_WIFI_ONLY, true);
+ boolean isWhileChargingOnly = intent.getBooleanExtra(KEY_WHILE_CHARGING_ONLY, false);
+ long uploadTimestamp = intent.getLongExtra(KEY_UPLOAD_TIMESTAMP, -1);
+
+ LocalBehaviour localAction = (LocalBehaviour) intent.getSerializableExtra(KEY_LOCAL_BEHAVIOUR);
+ if (localAction == null)
+ localAction = LocalBehaviour.LOCAL_BEHAVIOUR_COPY;
+
+ // save always persistently path of upload, so it can be retried if
+ // failed.
+ for (int i = 0; i < files.length; i++) {
+ UploadDbObject uploadObject = new UploadDbObject(files[i]);
+ uploadObject.setAccountName(account.name);
+ uploadObject.setForceOverwrite(forceOverwrite);
+ uploadObject.setCreateRemoteFolder(isCreateRemoteFolder);
+ uploadObject.setLocalAction(localAction);
+ uploadObject.setUseWifiOnly(isUseWifiOnly);
+ uploadObject.setWhileChargingOnly(isWhileChargingOnly);
+ uploadObject.setUploadTimestamp(uploadTimestamp);
+ uploadObject.setUploadStatus(UploadStatus.UPLOAD_LATER);
+
+ String uploadKey = buildRemoteName(uploadObject);
+ UploadDbObject previous = mPendingUploads.putIfAbsent(uploadKey, uploadObject);
+
+ if(previous == null)
+ {
+ Log_OC.d(TAG, "mPendingUploads added: " + uploadObject.toFormattedString());
+
+ // if upload was not queued before, we can add it to
+ // database because the user wants the file to be uploaded.
+ // however, it can happened that the user uploaded the same
+ // file before in which case there is an old db entry.
+ // delete that to be sure we have the latest one.
+ if(mDb.removeUpload(uploadObject.getLocalPath())>0) {
+ Log_OC.w(TAG, "There was an old DB entry " + uploadObject.getLocalPath()
+ + " which had to be removed in order to add new one.");
+ }
+ boolean success = mDb.storeUpload(uploadObject);
+ if(!success) {
+ Log_OC.e(TAG, "Could not add upload " + uploadObject.getLocalPath()
+ + " to database. This should not happen.");
+ }
+ } else {
+ Log_OC.w(TAG, "FileUploadService got upload intent for file which is already queued: "
+ + uploadObject.getRemotePath());
+ // upload already pending. ignore.
+ }
+ }
+ }
+ // at this point mPendingUploads is filled.
+
+
+
+ Iterator it;
+ if (intent != null && intent.getStringExtra(KEY_RETRY_REMOTE_PATH) != null) {
+ ArrayList list = new ArrayList(1);
+ String remotePath = intent.getStringExtra(KEY_RETRY_REMOTE_PATH);
+ list.add(remotePath);
+ it = list.iterator();
+ // update db status for upload
+ UploadDbObject uploadDbObject = mPendingUploads.get(remotePath);
+ uploadDbObject.setUploadStatus(UploadStatus.UPLOAD_LATER);
+ uploadDbObject.setLastResult(null);
+ mDb.updateUploadStatus(uploadDbObject);
+
+ Log_OC.d(TAG, "Start uploading " + remotePath);
+ } else {
+ it = mPendingUploads.keySet().iterator();
+ }
+ if (it.hasNext()) {
+ while (it.hasNext()) {
+ String upload = it.next();
+ UploadDbObject uploadDbObject = mPendingUploads.get(upload);
+
+ if (uploadDbObject == null) {
+ Log_OC.e(TAG, "Cannot upload null. Fix that!");
+ continue;
+ }
+
+ UploadTask uploadTask = new UploadTask(uploadDbObject);
+ uploadExecutor.submit(uploadTask);
+ }
+ StopSelfTask stopSelfTask = new StopSelfTask(intentStartId);
+ uploadExecutor.submit(stopSelfTask);
+
+ } else {
+ stopSelf(intentStartId);
+ }
+
+ Log_OC.d(TAG, "onHandleIntent end");
+ }
+
+ /**
+ * Stops this services if latest intent id is intentStartId.
+ */
+ public class StopSelfTask implements Runnable {
+ int intentStartId;
+
+ public StopSelfTask(int intentStartId) {
+ this.intentStartId = intentStartId;
+ }
+
+ @Override
+ public void run() {
+ stopSelf(intentStartId);
+ }
+ }
+
+ /**
+ * Tries uploading uploadDbObject, creates notifications, and updates mDb.
+ */
+ public class UploadTask implements Runnable {
+ UploadDbObject uploadDbObject;
+
+ public UploadTask(UploadDbObject uploadDbObject) {
+ this.uploadDbObject = uploadDbObject;
+ }
+
+ @Override
+ public void run() {
+ Log_OC.d(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - before uploading.");
+ switch (canUploadFileNow(uploadDbObject)) {
+ case NOW:
+ Log_OC.d(TAG, "Calling uploadFile for " + uploadDbObject.getRemotePath());
+ RemoteOperationResult uploadResult = uploadFile(uploadDbObject);
+ //TODO store renamed upload path?
+ updateDatabaseUploadResult(uploadResult, mCurrentUpload);
+ notifyUploadResult(uploadResult, mCurrentUpload);
+ sendFinalBroadcast(uploadResult, mCurrentUpload);
+ if (!shouldRetryFailedUpload(uploadResult)) {
+ Log_OC.d(TAG, "Upload with result " + uploadResult.getCode() + ": " + uploadResult.getLogMessage()
+ + " will be abandoned.");
+ mPendingUploads.remove(buildRemoteName(uploadDbObject));
+ }
+ Log_OC.d(TAG, "mCurrentUpload = null");
+ mCurrentUpload = null;
+ break;
+ case LATER:
+ // Schedule retry for later upload. Delay can be due to:
+ // KEY_WIFI_ONLY - done by ConnectivityActionReceiver
+ // KEY_WHILE_CHARGING_ONLY - TODO add PowerConnectionReceiver
+ // KEY_UPLOAD_TIMESTAMP - TODO use AlarmManager to wake up this service
+ break;
+ case FILE_GONE:
+ mDb.updateUploadStatus(uploadDbObject.getLocalPath(), UploadStatus.UPLOAD_FAILED_GIVE_UP,
+ new RemoteOperationResult(ResultCode.FILE_NOT_FOUND));
+ if (mPendingUploads.remove(uploadDbObject.getRemotePath()) == null) {
+ Log_OC.w(TAG, "Could remove " + uploadDbObject.getRemotePath()
+ + " from mPendingUploads because it does not exist.");
+ }
+
+ break;
+ case ERROR:
+ Log_OC.e(TAG, "canUploadFileNow() returned ERROR. Fix that!");
+ break;
+ }
+ Log_OC.d(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - after uploading.");
+ }
+
+ }
+
+ /**
+ * Returns the reason as String why state of uploadDbObject is LATER. If
+ * upload state != LATER return null.
+ */
+ static public String getUploadLaterReason(Context context, UploadDbObject uploadDbObject) {
+ StringBuilder reason = new StringBuilder();
+ Date now = new Date();
+ if (now.getTime() < uploadDbObject.getUploadTimestamp()) {
+ reason.append("Waiting for " + DisplayUtils.unixTimeToHumanReadable(uploadDbObject.getUploadTimestamp()));
+ }
+ if (uploadDbObject.isUseWifiOnly() && !UploadUtils.isConnectedViaWiFi(context)) {
+ if (reason.length() > 0) {
+ reason.append(" and wifi connectivity");
+ } else {
+ reason.append("Waiting for wifi connectivity");
+ }
+ }
+ if (uploadDbObject.isWhileChargingOnly() && !UploadUtils.isCharging(context)) {
+ if (reason.length() > 0) {
+ reason.append(" and charging");
+ } else {
+ reason.append("Waiting for charging");
+ }
+ }
+ reason.append(".");
+ if (reason.length() > 1) {
+ return reason.toString();
+ }
+ if (uploadDbObject.getUploadStatus() == UploadStatus.UPLOAD_LATER) {
+ return "Upload is pending and will start shortly.";
+ }
+ return null;
+ }
+
+ /**
+ * Provides a binder object that clients can use to perform operations on
+ * the queue of uploads, excepting the addition of new files.
+ *
+ * Implemented to perform cancellation, pause and resume of existing
+ * uploads.
+ */
+ @Override
+ public IBinder onBind(Intent arg0) {
+ return mBinder;
+ }
+
+ /**
+ * Called when ALL the bound clients were onbound.
+ */
+ @Override
+ public boolean onUnbind(Intent intent) {
+ ((FileUploaderBinder) mBinder).clearListeners();
+ return false; // not accepting rebinding (default behaviour)
+ }
+
+ /**
+ * Binder to let client components to perform operations on the queue of
+ * uploads.
+ *
+ * It provides by itself the available operations.
+ */
+ public class FileUploaderBinder extends Binder implements OnDatatransferProgressListener {
+
+ /**
+ * Map of listeners that will be reported about progress of uploads from
+ * a {@link FileUploaderBinder} instance
+ */
+ private Map mBoundListeners = new HashMap();
+
+ /**
+ * Returns ongoing upload operation. May be null.
+ */
+ public UploadFileOperation getCurrentUploadOperation() {
+ return mCurrentUpload;
+ }
+
+ /**
+ * Cancels a pending or current upload of a remote file.
+ *
+ * @param account Owncloud account where the remote file will be stored.
+ * @param file A file in the queue of pending uploads
+ */
+ public void cancel(Account account, OCFile file) {
+ // updating current references (i.e., uploadStatus of current
+ // upload) is handled by updateDataseUploadResult() which is called
+ // after upload finishes. Following cancel call makes sure that is
+ // does finish right now.
+ if (mCurrentUpload != null && mCurrentUpload.isUploadInProgress()) {
+ Log_OC.d(TAG, "Calling cancel for " + file.getRemotePath() + " during upload operation.");
+ mCurrentUpload.cancel();
+ } else if(mCancellationPossible.get()){
+ Log_OC.d(TAG, "Calling cancel for " + file.getRemotePath() + " during preparing for upload.");
+ mCancellationRequested.set(true);
+ } else {
+ Log_OC.d(TAG, "Calling cancel for " + file.getRemotePath() + " while upload is pending.");
+ // upload not in progress, but pending.
+ // in this case we have to update the db here.
+ UploadDbObject upload = mPendingUploads.remove(buildRemoteName(account, file));
+ upload.setUploadStatus(UploadStatus.UPLOAD_CANCELLED);
+ upload.setLastResult(new RemoteOperationResult(ResultCode.CANCELLED));
+ // storagePath inside upload is the temporary path. file
+ // contains the correct path used as db reference.
+ upload.getOCFile().setStoragePath(file.getStoragePath());
+ mDb.updateUploadStatus(upload);
+ }
+ }
+
+ /**
+ * Cancels a pending or current upload for an account
+ *
+ * @param account Owncloud accountName where the remote file will be stored.
+ */
+ public void cancel(Account account) {
+ Log_OC.d(TAG, "Account= " + account.name);
+
+ if (mCurrentUpload != null) {
+ Log_OC.d(TAG, "Current Upload Account= " + mCurrentUpload.getAccount().name);
+ if (mCurrentUpload.getAccount().name.equals(account.name)) {
+ mCurrentUpload.cancel();
+ }
+ }
+ // Cancel pending uploads
+ cancelUploadForAccount(account.name);
+ }
+
+ public void remove(Account account, OCFile file) {
+ UploadDbObject upload = mPendingUploads.remove(buildRemoteName(account, file));
+ if(upload == null) {
+ Log_OC.e(TAG, "Could not delete upload "+file+" from mPendingUploads.");
+ }
+ int d = mDb.removeUpload(file.getStoragePath());
+ if(d == 0) {
+ Log_OC.e(TAG, "Could not delete upload "+file.getStoragePath()+" from database.");
+ }
+ }
+
+ /**
+ * Puts upload in upload list and tell FileUploadService to upload items in list.
+ */
+ public void retry(Account account, UploadDbObject upload) {
+ String uploadKey = buildRemoteName(upload);
+ mPendingUploads.put(uploadKey, upload);
+ FileUploadService.retry(getApplicationContext(), uploadKey);
+ }
+
+ public void clearListeners() {
+ mBoundListeners.clear();
+ }
+
+ /**
+ * Returns True when the file described by 'file' is being uploaded to
+ * the ownCloud account 'account' or waiting for it
+ *
+ * If 'file' is a directory, returns 'true' if some of its descendant
+ * files is uploading or waiting to upload.
+ *
+ * Warning: If remote file exists and !forceOverwrite the original file
+ * is being returned here. That is, it seems as if the original file is
+ * being updated when actually a new file is being uploaded.
+ *
+ * @param account Owncloud account where the remote file will be stored.
+ * @param file A file that could be in the queue of pending uploads
+ */
+ public boolean isUploading(Account account, OCFile file) {
+ if (account == null || file == null)
+ return false;
+ String targetKey = buildRemoteName(account, file);
+ synchronized (mPendingUploads) {
+ if (file.isFolder()) {
+ // this can be slow if there are many uploads :(
+ Iterator it = mPendingUploads.keySet().iterator();
+ boolean found = false;
+ while (it.hasNext() && !found) {
+ found = it.next().startsWith(targetKey);
+ }
+ return found;
+ } else {
+ return (mPendingUploads.containsKey(targetKey));
+ }
+ }
+ }
+
+ /**
+ * Adds a listener interested in the progress of the upload for a
+ * concrete file.
+ *
+ * @param listener Object to notify about progress of transfer.
+ * @param account ownCloud account holding the file of interest.
+ * @param file {@link OCfile} of interest for listener.
+ */
+ public void addDatatransferProgressListener(OnDatatransferProgressListener listener, Account account,
+ OCFile file) {
+ if (account == null || file == null || listener == null)
+ return;
+ String targetKey = buildRemoteName(account, file);
+ mBoundListeners.put(targetKey, listener);
+ }
+
+ /**
+ * Removes a listener interested in the progress of the upload for a
+ * concrete file.
+ *
+ * @param listener Object to notify about progress of transfer.
+ * @param account ownCloud account holding the file of interest.
+ * @param file {@link OCfile} of interest for listener.
+ */
+ public void removeDatatransferProgressListener(OnDatatransferProgressListener listener, Account account,
+ OCFile file) {
+ if (account == null || file == null || listener == null)
+ return;
+ String targetKey = buildRemoteName(account, file);
+ if (mBoundListeners.get(targetKey) == listener) {
+ mBoundListeners.remove(targetKey);
+ }
+ }
+
+ @Override
+ public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer,
+ String localFileName) {
+ Set> uploads = mPendingUploads.entrySet();
+ UploadFileOperation currentUpload = mCurrentUpload;
+ if (currentUpload == null) {
+ Log_OC.e(TAG, "Found no current upload with remote path " + localFileName + ". Ignore.");
+ return;
+ }
+ String key = buildRemoteName(currentUpload.getAccount(), currentUpload.getFile());
+ OnDatatransferProgressListener boundListener = mBoundListeners.get(key);
+ if (boundListener != null) {
+ boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, localFileName);
+ }
+ }
+
+
+ }
+
+ enum CanUploadFileNowStatus {NOW, LATER, FILE_GONE, ERROR};
+
+ /**
+ * Returns true when the file may be uploaded now. This methods checks all
+ * restraints of the passed {@link UploadDbObject}, these include
+ * isUseWifiOnly(), check if local file exists, check if file was already
+ * uploaded...
+ *
+ * If return value is CanUploadFileNowStatus.NOW, uploadFile() may be
+ * called.
+ *
+ * @return CanUploadFileNowStatus.NOW is upload may proceed,
+ * CanUploadFileNowStatus.LATER if upload should be performed at a
+ * later time,
+ * CanUploadFileNowStatus.ERROR if a severe error happened, calling
+ * entity should remove upload from queue.
+ *
+ */
+ private CanUploadFileNowStatus canUploadFileNow(UploadDbObject uploadDbObject) {
+
+ if (uploadDbObject.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) {
+ Log_OC.w(TAG, "Already succeeded uploadObject was again scheduled for upload. Fix that!");
+ return CanUploadFileNowStatus.ERROR;
+ }
+
+ if (uploadDbObject.isUseWifiOnly()
+ && !UploadUtils.isConnectedViaWiFi(getApplicationContext())) {
+ Log_OC.d(TAG, "Do not start upload because it is wifi-only.");
+ return CanUploadFileNowStatus.LATER;
+ }
+
+ if(uploadDbObject.isWhileChargingOnly() && !UploadUtils.isCharging(getApplicationContext())) {
+ Log_OC.d(TAG, "Do not start upload because it is while charging only.");
+ return CanUploadFileNowStatus.LATER;
+ }
+ Date now = new Date();
+ if (now.getTime() < uploadDbObject.getUploadTimestamp()) {
+ Log_OC.d(
+ TAG,
+ "Do not start upload because it is schedule for "
+ + DisplayUtils.unixTimeToHumanReadable(uploadDbObject.getUploadTimestamp()));
+ return CanUploadFileNowStatus.LATER;
+ }
+
+
+ if (!new File(uploadDbObject.getLocalPath()).exists()) {
+ Log_OC.d(TAG, "Do not start upload because local file does not exist.");
+ return CanUploadFileNowStatus.FILE_GONE;
+ }
+ return CanUploadFileNowStatus.NOW;
+ }
+
+
+
+ /**
+ * Core upload method: sends the file(s) to upload. This function blocks
+ * until upload succeeded or failed.
+ *
+ * Before uploading starts mCurrentUpload is set.
+ *
+ * @param uploadDbObject Key to access the upload to perform, contained in
+ * mPendingUploads
+ * @return RemoteOperationResult of upload operation.
+ */
+ private RemoteOperationResult uploadFile(UploadDbObject uploadDbObject) {
+
+ if (mCurrentUpload != null) {
+ Log_OC.e(TAG,
+ "mCurrentUpload != null. Meaning there is another upload in progress, cannot start a new one. Fix that!");
+ return new RemoteOperationResult(new IllegalStateException("mCurrentUpload != null when calling uploadFile()"));
+ }
+
+ AccountManager aMgr = AccountManager.get(this);
+ Account account = uploadDbObject.getAccount(getApplicationContext());
+ String version = aMgr.getUserData(account, Constants.KEY_OC_VERSION);
+ OwnCloudVersion ocv = new OwnCloudVersion(version);
+
+ boolean chunked = FileUploadService.chunkedUploadIsSupported(ocv);
+ String uploadKey = null;
+
+ uploadKey = buildRemoteName(account, uploadDbObject.getRemotePath());
+ OCFile file = uploadDbObject.getOCFile();
+ Log_OC.d(TAG, "mCurrentUpload = new UploadFileOperation");
+ mCurrentUpload = new UploadFileOperation(account, file, chunked, uploadDbObject.isForceOverwrite(),
+ uploadDbObject.getLocalAction(), getApplicationContext());
+ if (uploadDbObject.isCreateRemoteFolder()) {
+ mCurrentUpload.setRemoteFolderToBeCreated();
+ }
+ mCurrentUpload.addDatatransferProgressListener((FileUploaderBinder) mBinder);
+ mCurrentUpload.addDatatransferProgressListener(this);
+
+ mCancellationRequested.set(false);
+ mCancellationPossible.set(true);
+ notifyUploadStart(mCurrentUpload);
+
+ RemoteOperationResult uploadResult = null, grantResult = null;
+ try {
+ // prepare client object to send requests to the ownCloud
+ // server
+ if (mUploadClient == null || !mLastAccount.equals(mCurrentUpload.getAccount())) {
+ mLastAccount = mCurrentUpload.getAccount();
+ mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver());
+ OwnCloudAccount ocAccount = new OwnCloudAccount(mLastAccount, this);
+ mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, this);
+ }
+
+ // check the existence of the parent folder for the file to
+ // upload
+ String remoteParentPath = new File(mCurrentUpload.getRemotePath()).getParent();
+ remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ? remoteParentPath : remoteParentPath
+ + OCFile.PATH_SEPARATOR;
+ //TODO this might take a moment and should thus be cancelable, for now: cancel afterwards.
+ grantResult = grantFolderExistence(mCurrentUpload, remoteParentPath);
+
+ if(mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
+ mCancellationPossible.set(false);
+
+ // perform the upload
+ if (grantResult.isSuccess()) {
+ OCFile parent = mStorageManager.getFileByPath(remoteParentPath);
+ mCurrentUpload.getFile().setParentId(parent.getFileId());
+ // inside this call the remote path may be changed (if remote
+ // file exists and !forceOverwrite) in this case the map from
+ // mPendingUploads is wrong. This results in an upload
+ // indication in the GUI showing that the original file is being
+ // uploaded (instead that a new one is created)
+ uploadResult = mCurrentUpload.execute(mUploadClient);
+ if (uploadResult.isSuccess()) {
+ saveUploadedFile(mCurrentUpload);
+ }
+ } else {
+ uploadResult = grantResult;
+ }
+
+ } catch (AccountsException e) {
+ Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);
+ uploadResult = new RemoteOperationResult(e);
+
+ } catch (IOException e) {
+ Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);
+ uploadResult = new RemoteOperationResult(e);
+
+ } catch (OperationCancelledException e) {
+ uploadResult = new RemoteOperationResult(e);
+ } finally {
+ Log_OC.d(TAG, "Remove CurrentUploadItem from pending upload Item Map.");
+ if (uploadResult.isException()) {
+ // enforce the creation of a new client object for next
+ // uploads; this grant that a new socket will
+ // be created in the future if the current exception is due
+ // to an abrupt lose of network connection
+ mUploadClient = null;
+ }
+ }
+ return uploadResult;
+ }
+
+
+ /**
+ * Checks the existence of the folder where the current file will be
+ * uploaded both in the remote server and in the local database.
+ *
+ * If the upload is set to enforce the creation of the folder, the method
+ * tries to create it both remote and locally.
+ *
+ * @param pathToGrant Full remote path whose existence will be granted.
+ * @return An {@link OCFile} instance corresponding to the folder where the
+ * file will be uploaded.
+ */
+ private RemoteOperationResult grantFolderExistence(UploadFileOperation currentUpload, String pathToGrant) {
+ RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, this, false);
+ RemoteOperationResult result = operation.execute(mUploadClient);
+ if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND
+ && currentUpload.isRemoteFolderToBeCreated()) {
+ SyncOperation syncOp = new CreateFolderOperation(pathToGrant, true);
+ result = syncOp.execute(mUploadClient, mStorageManager);
+ }
+ if (result.isSuccess()) {
+ OCFile parentDir = mStorageManager.getFileByPath(pathToGrant);
+ if (parentDir == null) {
+ parentDir = createLocalFolder(pathToGrant);
+ }
+ if (parentDir != null) {
+ result = new RemoteOperationResult(ResultCode.OK);
+ } else {
+ result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
+ }
+ }
+ return result;
+ }
+
+ private OCFile createLocalFolder(String remotePath) {
+ String parentPath = new File(remotePath).getParent();
+ parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
+ OCFile parent = mStorageManager.getFileByPath(parentPath);
+ if (parent == null) {
+ parent = createLocalFolder(parentPath);
+ }
+ if (parent != null) {
+ OCFile createdFolder = new OCFile(remotePath);
+ createdFolder.setMimetype("DIR");
+ createdFolder.setParentId(parent.getFileId());
+ mStorageManager.saveFile(createdFolder);
+ return createdFolder;
+ }
+ return null;
+ }
+
+ /**
+ * Saves a OC File after a successful upload.
+ *
+ * A PROPFIND is necessary to keep the props in the local database
+ * synchronized with the server, specially the modification time and Etag
+ * (where available)
+ *
+ * TODO refactor this ugly thing
+ */
+ private void saveUploadedFile(UploadFileOperation currentUpload) {
+ OCFile file = currentUpload.getFile();
+ if (file.fileExists()) {
+ file = mStorageManager.getFileById(file.getFileId());
+ }
+ long syncDate = System.currentTimeMillis();
+ file.setLastSyncDateForData(syncDate);
+
+ // new PROPFIND to keep data consistent with server
+ // in theory, should return the same we already have
+ ReadRemoteFileOperation operation = new ReadRemoteFileOperation(currentUpload.getRemotePath());
+ RemoteOperationResult result = operation.execute(mUploadClient);
+ if (result.isSuccess()) {
+ updateOCFile(file, (RemoteFile) result.getData().get(0));
+ file.setLastSyncDateForProperties(syncDate);
+ }
+
+ // / maybe this would be better as part of UploadFileOperation... or
+ // maybe all this method
+ if (currentUpload.wasRenamed()) {
+ OCFile oldFile = currentUpload.getOldFile();
+ if (oldFile.fileExists()) {
+ oldFile.setStoragePath(null);
+ mStorageManager.saveFile(oldFile);
+
+ } // else: it was just an automatic renaming due to a name
+ // coincidence; nothing else is needed, the storagePath is right
+ // in the instance returned by mCurrentUpload.getFile()
+ }
+ file.setNeedsUpdateThumbnail(true);
+ mStorageManager.saveFile(file);
+ }
+
+ private void updateOCFile(OCFile file, RemoteFile remoteFile) {
+ file.setCreationTimestamp(remoteFile.getCreationTimestamp());
+ file.setFileLength(remoteFile.getLength());
+ file.setMimetype(remoteFile.getMimeType());
+ file.setModificationTimestamp(remoteFile.getModifiedTimestamp());
+ file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp());
+ // file.setEtag(remoteFile.getEtag()); // TODO Etag, where available
+ file.setRemoteId(remoteFile.getRemoteId());
+ }
+
+
+ private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType) {
+
+ // MIME type
+ if (mimeType == null || mimeType.length() <= 0) {
+ try {
+ mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+ remotePath.substring(remotePath.lastIndexOf('.') + 1));
+ } catch (IndexOutOfBoundsException e) {
+ Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + remotePath);
+ }
+ }
+ if (mimeType == null) {
+ mimeType = "application/octet-stream";
+ }
+
+ if (isPdfFileFromContentProviderWithoutExtension(localPath, mimeType)){
+ remotePath += FILE_EXTENSION_PDF;
+ }
+
+ OCFile newFile = new OCFile(remotePath);
+ newFile.setStoragePath(localPath);
+ newFile.setLastSyncDateForProperties(0);
+ newFile.setLastSyncDateForData(0);
+
+ // size
+ if (localPath != null && localPath.length() > 0) {
+ File localFile = new File(localPath);
+ newFile.setFileLength(localFile.length());
+ newFile.setLastSyncDateForData(localFile.lastModified());
+ } // don't worry about not assigning size, the problems with localPath
+ // are checked when the UploadFileOperation instance is created
+
+
+ newFile.setMimetype(mimeType);
+
+ return newFile;
+ }
+
+ /**
+ * Creates a status notification to show the upload progress
+ *
+ * @param upload Upload operation starting.
+ */
+ private void notifyUploadStart(UploadFileOperation upload) {
+ // / create status notification with a progress bar
+ mLastPercent = 0;
+ mNotificationBuilder = NotificationBuilderWithProgressBar.newNotificationBuilderWithProgressBar(this);
+ mNotificationBuilder
+ .setOngoing(true)
+ .setSmallIcon(R.drawable.notification_icon)
+ .setTicker(getString(R.string.uploader_upload_in_progress_ticker))
+ .setContentTitle(getString(R.string.uploader_upload_in_progress_ticker))
+ .setProgress(100, 0, false)
+ .setContentText(
+ String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName()));
+
+// // / includes a pending intent in the notification showing the details
+// // view of the file
+// Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class);
+// showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable)upload.getFile());
+// showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, upload.getAccount());
+// showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ Intent showUploadListIntent = new Intent(this, UploadListActivity.class);
+ showUploadListIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable)upload.getFile());
+ showUploadListIntent.putExtra(FileActivity.EXTRA_ACCOUNT, upload.getAccount());
+ showUploadListIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ showUploadListIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this, (int) System.currentTimeMillis(),
+ showUploadListIntent, 0));
+
+ mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build());
+
+ updateDatabaseUploadStart(mCurrentUpload);
+ }
+
+ /**
+ * Updates the persistent upload database that upload is in progress.
+ */
+ private void updateDatabaseUploadStart(UploadFileOperation upload) {
+ mDb.updateUploadStatus(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_IN_PROGRESS, null);
+ }
+
+ /**
+ * Callback method to update the progress bar in the status notification
+ */
+ @Override
+ public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filePath) {
+ int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
+ if (percent != mLastPercent) {
+ mNotificationBuilder.setProgress(100, percent, false);
+ String fileName = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1);
+ String text = String.format(getString(R.string.uploader_upload_in_progress_content), percent, fileName);
+ mNotificationBuilder.setContentText(text);
+ mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build());
+ }
+ mLastPercent = percent;
+ }
+
+ /**
+ * Updates the status notification with the result of an upload operation.
+ *
+ * @param uploadResult Result of the upload operation.
+ * @param upload Finished upload operation
+ */
+ private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOperation upload) {
+ Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.getCode());
+ // / cancelled operation or success -> silent removal of progress
+ // notification
+ mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker);
+
+ // Show the result: success or fail notification
+ if (!uploadResult.isCancelled()) {
+ int tickerId = (uploadResult.isSuccess()) ? R.string.uploader_upload_succeeded_ticker
+ : R.string.uploader_upload_failed_ticker;
+
+ String content = null;
+
+ // check credentials error
+ boolean needsToUpdateCredentials = (uploadResult.getCode() == ResultCode.UNAUTHORIZED || uploadResult
+ .isIdPRedirection());
+ tickerId = (needsToUpdateCredentials) ? R.string.uploader_upload_failed_credentials_error : tickerId;
+
+ mNotificationBuilder.setTicker(getString(tickerId)).setContentTitle(getString(tickerId))
+ .setAutoCancel(true).setOngoing(false).setProgress(0, 0, false);
+
+ content = ErrorMessageAdapter.getErrorCauseMessage(uploadResult, upload, getResources());
+
+ if (needsToUpdateCredentials) {
+ // let the user update credentials with one click
+ Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
+ updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, upload.getAccount());
+ updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACTION,
+ AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN);
+ updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);
+ mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this, (int) System.currentTimeMillis(),
+ updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT));
+
+ mUploadClient = null;
+ // grant that future retries on the same account will get the
+ // fresh credentials
+
+ }
+
+ if(!uploadResult.isSuccess()){
+ //in case of failure, do not show details file view (because there is no file!)
+ Intent showUploadListIntent = new Intent(this, UploadListActivity.class);
+ showUploadListIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable)upload.getFile());
+ showUploadListIntent.putExtra(FileActivity.EXTRA_ACCOUNT, upload.getAccount());
+ mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this, (int) System.currentTimeMillis(),
+ showUploadListIntent, 0));
+ }
+
+ mNotificationBuilder.setContentText(content);
+ mNotificationManager.notify(tickerId, mNotificationBuilder.build());
+
+ if (uploadResult.isSuccess()) {
+
+ // remove success notification, with a delay of 2 seconds
+ NotificationDelayer.cancelWithDelay(mNotificationManager, R.string.uploader_upload_succeeded_ticker,
+ 2000);
+ }
+ }
+ }
+
+ /**
+ * Updates the persistent upload database with upload result.
+ */
+ private void updateDatabaseUploadResult(RemoteOperationResult uploadResult, UploadFileOperation upload) {
+ // result: success or fail notification
+ Log_OC.d(TAG, "updateDataseUploadResult uploadResult: " + uploadResult + " upload: " + upload);
+ if (uploadResult.isCancelled()) {
+ mDb.updateUploadStatus(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_CANCELLED, uploadResult);
+ } else {
+
+ if (uploadResult.isSuccess()) {
+ mDb.updateUploadStatus(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_SUCCEEDED, uploadResult);
+ } else {
+ if (shouldRetryFailedUpload(uploadResult)) {
+ mDb.updateUploadStatus(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED_RETRY, uploadResult);
+ } else {
+ mDb.updateUploadStatus(upload.getOriginalStoragePath(),
+ UploadDbHandler.UploadStatus.UPLOAD_FAILED_GIVE_UP, uploadResult);
+ }
+ }
+ }
+ }
+
+ /**
+ * Determines whether with given uploadResult the upload should be retried later.
+ * @param uploadResult
+ * @return true if upload should be retried later, false if is should be abandoned.
+ */
+ private boolean shouldRetryFailedUpload(RemoteOperationResult uploadResult) {
+ if (uploadResult.isSuccess()) {
+ return false;
+ }
+ switch (uploadResult.getCode()) {
+ case HOST_NOT_AVAILABLE:
+ case NO_NETWORK_CONNECTION:
+ case TIMEOUT:
+ case WRONG_CONNECTION: // SocketException
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Sends a broadcast in order to the interested activities can update their
+ * view
+ * @param uploadResult Result of the upload operation
+ * @param upload Finished upload operation
+ */
+ private void sendFinalBroadcast(RemoteOperationResult uploadResult, UploadFileOperation upload) {
+ Intent end = new Intent(getUploadFinishMessage());
+ end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote
+ // path, after
+ // possible
+ // automatic
+ // renaming
+ if (upload.wasRenamed()) {
+ end.putExtra(EXTRA_OLD_REMOTE_PATH, upload.getOldFile().getRemotePath());
+ }
+ end.putExtra(EXTRA_OLD_FILE_PATH, upload.getOriginalStoragePath());
+ end.putExtra(ACCOUNT_NAME, upload.getAccount().name);
+ end.putExtra(EXTRA_UPLOAD_RESULT, uploadResult.isSuccess());
+ sendStickyBroadcast(end);
+ }
+
+ /**
+ * Checks if content provider, using the content:// scheme, returns a file with mime-type
+ * 'application/pdf' but file has not extension
+ * @param localPath
+ * @param mimeType
+ * @return true if is needed to add the pdf file extension to the file
+ */
+ private boolean isPdfFileFromContentProviderWithoutExtension(String localPath, String mimeType) {
+ return localPath.startsWith(UriUtils.URI_CONTENT_SCHEME) &&
+ mimeType.equals(MIME_TYPE_PDF) &&
+ !localPath.endsWith(FILE_EXTENSION_PDF);
+ }
+
+ /**
+ * Remove uploads of an account
+ * @param accountName Name of an OC account
+ */
+ private void cancelUploadForAccount(String accountName){
+ // this can be slow if there are many uploads :(
+ Iterator it = mPendingUploads.keySet().iterator();
+ Log_OC.d(TAG, "Number of pending updloads= " + mPendingUploads.size());
+ while (it.hasNext()) {
+ String key = it.next();
+ Log_OC.d(TAG, "mPendingUploads CANCELLED " + key);
+ if (key.startsWith(accountName)) {
+ synchronized (mPendingUploads) {
+ mPendingUploads.remove(key);
+ }
+ }
+ }
+ }
+
+ /**
+ * Call if all pending uploads are to be retried.
+ */
+ public static void retry(Context context) {
+ retry(context, null);
+ }
+
+ /**
+ * Call to retry upload identified by remotePath
+ */
+ private static void retry(Context context, String remotePath) {
+ Log_OC.d(TAG, "FileUploadService.retry()");
+ Intent i = new Intent(context, FileUploadService.class);
+ i.putExtra(FileUploadService.KEY_RETRY, true);
+ if(remotePath != null) {
+ i.putExtra(FileUploadService.KEY_RETRY_REMOTE_PATH, remotePath);
+ }
+ context.startService(i);
+ }
+
+}
diff --git a/src/com/owncloud/android/files/services/FileUploader.java b/src/com/owncloud/android/files/services/FileUploader.java
deleted file mode 100644
index 98ad87e2db3..00000000000
--- a/src/com/owncloud/android/files/services/FileUploader.java
+++ /dev/null
@@ -1,994 +0,0 @@
-/**
- * ownCloud Android client application
- *
- * Copyright (C) 2012 Bartek Przybylski
- * Copyright (C) 2012-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 .
- *
- */
-
-package com.owncloud.android.files.services;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.AbstractList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Vector;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.AccountsException;
-import android.accounts.OnAccountsUpdateListener;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.support.v4.app.NotificationCompat;
-import android.webkit.MimeTypeMap;
-
-import com.owncloud.android.R;
-import com.owncloud.android.authentication.AccountUtils;
-import com.owncloud.android.authentication.AuthenticatorActivity;
-import com.owncloud.android.datamodel.FileDataStorageManager;
-import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.db.DbHandler;
-import com.owncloud.android.lib.common.OwnCloudAccount;
-import com.owncloud.android.lib.common.OwnCloudClient;
-import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
-import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
-import com.owncloud.android.lib.common.operations.RemoteOperation;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
-import com.owncloud.android.lib.resources.files.FileUtils;
-import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
-import com.owncloud.android.lib.resources.files.RemoteFile;
-import com.owncloud.android.lib.resources.status.OwnCloudVersion;
-import com.owncloud.android.notifications.NotificationBuilderWithProgressBar;
-import com.owncloud.android.notifications.NotificationDelayer;
-import com.owncloud.android.operations.CreateFolderOperation;
-import com.owncloud.android.operations.UploadFileOperation;
-import com.owncloud.android.operations.common.SyncOperation;
-import com.owncloud.android.ui.activity.FileActivity;
-import com.owncloud.android.ui.activity.FileDisplayActivity;
-import com.owncloud.android.utils.ErrorMessageAdapter;
-import com.owncloud.android.utils.UriUtils;
-
-
-public class FileUploader extends Service
- implements OnDatatransferProgressListener, OnAccountsUpdateListener {
-
- private static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH";
- public static final String EXTRA_UPLOAD_RESULT = "RESULT";
- public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
- public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH";
- public static final String EXTRA_OLD_FILE_PATH = "OLD_FILE_PATH";
- public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
-
- public static final String KEY_FILE = "FILE";
- public static final String KEY_LOCAL_FILE = "LOCAL_FILE";
- public static final String KEY_REMOTE_FILE = "REMOTE_FILE";
- public static final String KEY_MIME_TYPE = "MIME_TYPE";
-
- public static final String KEY_ACCOUNT = "ACCOUNT";
-
- public static final String KEY_UPLOAD_TYPE = "UPLOAD_TYPE";
- public static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE";
- public static final String KEY_INSTANT_UPLOAD = "INSTANT_UPLOAD";
- public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR";
-
- public static final int LOCAL_BEHAVIOUR_COPY = 0;
- public static final int LOCAL_BEHAVIOUR_MOVE = 1;
- public static final int LOCAL_BEHAVIOUR_FORGET = 2;
-
- public static final int UPLOAD_SINGLE_FILE = 0;
- public static final int UPLOAD_MULTIPLE_FILES = 1;
-
- private static final String TAG = FileUploader.class.getSimpleName();
-
- private Looper mServiceLooper;
- private ServiceHandler mServiceHandler;
- private IBinder mBinder;
- private OwnCloudClient mUploadClient = null;
- private Account mLastAccount = null;
- private FileDataStorageManager mStorageManager;
-
- private ConcurrentMap mPendingUploads =
- new ConcurrentHashMap();
- private UploadFileOperation mCurrentUpload = null;
-
- private NotificationManager mNotificationManager;
- private NotificationCompat.Builder mNotificationBuilder;
- private int mLastPercent;
-
- private static final String MIME_TYPE_PDF = "application/pdf";
- private static final String FILE_EXTENSION_PDF = ".pdf";
-
-
- public static String getUploadFinishMessage() {
- return FileUploader.class.getName() + UPLOAD_FINISH_MESSAGE;
- }
-
- /**
- * Builds a key for mPendingUploads from the account and file to upload
- *
- * @param account Account where the file to upload is stored
- * @param file File to upload
- */
- private String buildRemoteName(Account account, OCFile file) {
- return account.name + file.getRemotePath();
- }
-
- private String buildRemoteName(Account account, String remotePath) {
- return account.name + remotePath;
- }
-
- /**
- * Checks if an ownCloud server version should support chunked uploads.
- *
- * @param version OwnCloud version instance corresponding to an ownCloud
- * server.
- * @return 'True' if the ownCloud server with version supports chunked
- * uploads.
- */
- private static boolean chunkedUploadIsSupported(OwnCloudVersion version) {
- return (version != null && version.compareTo(OwnCloudVersion.owncloud_v4_5) >= 0);
- }
-
- /**
- * Service initialization
- */
- @Override
- public void onCreate() {
- super.onCreate();
- Log_OC.d(TAG, "Creating service");
- mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- HandlerThread thread = new HandlerThread("FileUploaderThread",
- Process.THREAD_PRIORITY_BACKGROUND);
- thread.start();
- mServiceLooper = thread.getLooper();
- mServiceHandler = new ServiceHandler(mServiceLooper, this);
- mBinder = new FileUploaderBinder();
-
- // add AccountsUpdatedListener
- AccountManager am = AccountManager.get(getApplicationContext());
- am.addOnAccountsUpdatedListener(this, null, false);
- }
-
- /**
- * Service clean up
- */
- @Override
- public void onDestroy() {
- Log_OC.v(TAG, "Destroying service" );
- mBinder = null;
- mServiceHandler = null;
- mServiceLooper.quit();
- mServiceLooper = null;
- mNotificationManager = null;
-
- // remove AccountsUpdatedListener
- AccountManager am = AccountManager.get(getApplicationContext());
- am.removeOnAccountsUpdatedListener(this);
-
- super.onDestroy();
- }
-
-
- /**
- * Entry point to add one or several files to the queue of uploads.
- *
- * New uploads are added calling to startService(), resulting in a call to
- * this method. This ensures the service will keep on working although the
- * caller activity goes away.
- */
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log_OC.d(TAG, "Starting command with id " + startId);
-
- if (!intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_UPLOAD_TYPE)
- || !(intent.hasExtra(KEY_LOCAL_FILE) || intent.hasExtra(KEY_FILE))) {
- Log_OC.e(TAG, "Not enough information provided in intent");
- return Service.START_NOT_STICKY;
- }
- int uploadType = intent.getIntExtra(KEY_UPLOAD_TYPE, -1);
- if (uploadType == -1) {
- Log_OC.e(TAG, "Incorrect upload type provided");
- return Service.START_NOT_STICKY;
- }
- Account account = intent.getParcelableExtra(KEY_ACCOUNT);
- if (!AccountUtils.exists(account, getApplicationContext())) {
- return Service.START_NOT_STICKY;
- }
-
- String[] localPaths = null, remotePaths = null, mimeTypes = null;
- OCFile[] files = null;
- if (uploadType == UPLOAD_SINGLE_FILE) {
-
- if (intent.hasExtra(KEY_FILE)) {
- files = new OCFile[] { intent.getParcelableExtra(KEY_FILE) };
-
- } else {
- localPaths = new String[] { intent.getStringExtra(KEY_LOCAL_FILE) };
- remotePaths = new String[] { intent.getStringExtra(KEY_REMOTE_FILE) };
- mimeTypes = new String[] { intent.getStringExtra(KEY_MIME_TYPE) };
- }
-
- } else { // mUploadType == UPLOAD_MULTIPLE_FILES
-
- if (intent.hasExtra(KEY_FILE)) {
- files = (OCFile[]) intent.getParcelableArrayExtra(KEY_FILE); // TODO
- // will
- // this
- // casting
- // work
- // fine?
-
- } else {
- localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE);
- remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE);
- mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE);
- }
- }
-
- FileDataStorageManager storageManager = new FileDataStorageManager(account,
- getContentResolver());
-
- boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false);
- boolean isInstant = intent.getBooleanExtra(KEY_INSTANT_UPLOAD, false);
- int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_COPY);
-
- if (intent.hasExtra(KEY_FILE) && files == null) {
- Log_OC.e(TAG, "Incorrect array for OCFiles provided in upload intent");
- return Service.START_NOT_STICKY;
-
- } else if (!intent.hasExtra(KEY_FILE)) {
- if (localPaths == null) {
- Log_OC.e(TAG, "Incorrect array for local paths provided in upload intent");
- return Service.START_NOT_STICKY;
- }
- if (remotePaths == null) {
- Log_OC.e(TAG, "Incorrect array for remote paths provided in upload intent");
- return Service.START_NOT_STICKY;
- }
- if (localPaths.length != remotePaths.length) {
- Log_OC.e(TAG, "Different number of remote paths and local paths!");
- return Service.START_NOT_STICKY;
- }
-
- files = new OCFile[localPaths.length];
- for (int i = 0; i < localPaths.length; i++) {
- files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i],
- ((mimeTypes != null) ? mimeTypes[i] : null), storageManager);
- if (files[i] == null) {
- // TODO @andomaex add failure Notification
- return Service.START_NOT_STICKY;
- }
- }
- }
-
- OwnCloudVersion ocv = AccountUtils.getServerVersion(account);
-
- boolean chunked = FileUploader.chunkedUploadIsSupported(ocv);
- AbstractList requestedUploads = new Vector();
- String uploadKey = null;
- UploadFileOperation newUpload = null;
- try {
- for (int i = 0; i < files.length; i++) {
- uploadKey = buildRemoteName(account, files[i].getRemotePath());
- newUpload = new UploadFileOperation(account, files[i], chunked, isInstant,
- forceOverwrite, localAction,
- getApplicationContext());
- if (isInstant) {
- newUpload.setRemoteFolderToBeCreated();
- }
- // Grants that the file only upload once time
- mPendingUploads.putIfAbsent(uploadKey, newUpload);
-
- newUpload.addDatatransferProgressListener(this);
- newUpload.addDatatransferProgressListener((FileUploaderBinder)mBinder);
- requestedUploads.add(uploadKey);
- }
-
- } catch (IllegalArgumentException e) {
- Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
- return START_NOT_STICKY;
-
- } catch (IllegalStateException e) {
- Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
- return START_NOT_STICKY;
-
- } catch (Exception e) {
- Log_OC.e(TAG, "Unexpected exception while processing upload intent", e);
- return START_NOT_STICKY;
-
- }
-
- if (requestedUploads.size() > 0) {
- Message msg = mServiceHandler.obtainMessage();
- msg.arg1 = startId;
- msg.obj = requestedUploads;
- mServiceHandler.sendMessage(msg);
- }
- Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size());
- return Service.START_NOT_STICKY;
- }
-
- /**
- * Provides a binder object that clients can use to perform operations on
- * the queue of uploads, excepting the addition of new files.
- *
- * Implemented to perform cancellation, pause and resume of existing
- * uploads.
- */
- @Override
- public IBinder onBind(Intent arg0) {
- return mBinder;
- }
-
- /**
- * Called when ALL the bound clients were onbound.
- */
- @Override
- public boolean onUnbind(Intent intent) {
- ((FileUploaderBinder)mBinder).clearListeners();
- return false; // not accepting rebinding (default behaviour)
- }
-
- @Override
- public void onAccountsUpdated(Account[] accounts) {
- // Review current upload, and cancel it if its account doen't exist
- if (mCurrentUpload != null &&
- !AccountUtils.exists(mCurrentUpload.getAccount(), getApplicationContext())) {
- mCurrentUpload.cancel();
- }
- // The rest of uploads are cancelled when they try to start
- }
-
- /**
- * Binder to let client components to perform operations on the queue of
- * uploads.
- *
- * It provides by itself the available operations.
- */
- public class FileUploaderBinder extends Binder implements OnDatatransferProgressListener {
-
- /**
- * Map of listeners that will be reported about progress of uploads from a
- * {@link FileUploaderBinder} instance
- */
- private Map mBoundListeners =
- new HashMap();
-
- /**
- * Cancels a pending or current upload of a remote file.
- *
- * @param account Owncloud account where the remote file will be stored.
- * @param file A file in the queue of pending uploads
- */
- public void cancel(Account account, OCFile file) {
- UploadFileOperation upload;
- synchronized (mPendingUploads) {
- upload = mPendingUploads.remove(buildRemoteName(account, file));
- }
- if (upload != null) {
- upload.cancel();
- }
- }
-
- /**
- * Cancels a pending or current upload for an account
- *
- * @param account Owncloud accountName where the remote file will be stored.
- */
- public void cancel(Account account) {
- Log_OC.d(TAG, "Account= " + account.name);
-
- if (mCurrentUpload != null) {
- Log_OC.d(TAG, "Current Upload Account= " + mCurrentUpload.getAccount().name);
- if (mCurrentUpload.getAccount().name.equals(account.name)) {
- mCurrentUpload.cancel();
- }
- }
- // Cancel pending uploads
- cancelUploadForAccount(account.name);
- }
-
- public void clearListeners() {
- mBoundListeners.clear();
- }
-
- /**
- * Returns True when the file described by 'file' is being uploaded to
- * the ownCloud account 'account' or waiting for it
- *
- * If 'file' is a directory, returns 'true' if some of its descendant files
- * is uploading or waiting to upload.
- *
- * @param account ownCloud account where the remote file will be stored.
- * @param file A file that could be in the queue of pending uploads
- */
- public boolean isUploading(Account account, OCFile file) {
- if (account == null || file == null)
- return false;
- String targetKey = buildRemoteName(account, file);
- synchronized (mPendingUploads) {
- if (file.isFolder()) {
- // this can be slow if there are many uploads :(
- Iterator it = mPendingUploads.keySet().iterator();
- boolean found = false;
- while (it.hasNext() && !found) {
- found = it.next().startsWith(targetKey);
- }
- return found;
- } else {
- return (mPendingUploads.containsKey(targetKey));
- }
- }
- }
-
-
- /**
- * Adds a listener interested in the progress of the upload for a concrete file.
- *
- * @param listener Object to notify about progress of transfer.
- * @param account ownCloud account holding the file of interest.
- * @param file {@link OCFile} of interest for listener.
- */
- public void addDatatransferProgressListener (OnDatatransferProgressListener listener,
- Account account, OCFile file) {
- if (account == null || file == null || listener == null) return;
- String targetKey = buildRemoteName(account, file);
- mBoundListeners.put(targetKey, listener);
- }
-
-
-
- /**
- * Removes a listener interested in the progress of the upload for a concrete file.
- *
- * @param listener Object to notify about progress of transfer.
- * @param account ownCloud account holding the file of interest.
- * @param file {@link OCFile} of interest for listener.
- */
- public void removeDatatransferProgressListener (OnDatatransferProgressListener listener,
- Account account, OCFile file) {
- if (account == null || file == null || listener == null) return;
- String targetKey = buildRemoteName(account, file);
- if (mBoundListeners.get(targetKey) == listener) {
- mBoundListeners.remove(targetKey);
- }
- }
-
-
- @Override
- public void onTransferProgress(long progressRate, long totalTransferredSoFar,
- long totalToTransfer, String fileName) {
- String key = buildRemoteName(mCurrentUpload.getAccount(), mCurrentUpload.getFile());
- OnDatatransferProgressListener boundListener = mBoundListeners.get(key);
- if (boundListener != null) {
- boundListener.onTransferProgress(progressRate, totalTransferredSoFar,
- totalToTransfer, fileName);
- }
- }
-
- /**
- * Review uploads and cancel it if its account doesn't exist
- */
- public void checkAccountOfCurrentUpload() {
- if (mCurrentUpload != null &&
- !AccountUtils.exists(mCurrentUpload.getAccount(), getApplicationContext())) {
- mCurrentUpload.cancel();
- }
- // The rest of uploads are cancelled when they try to start
- }
- }
-
- /**
- * Upload worker. Performs the pending uploads in the order they were
- * requested.
- *
- * Created with the Looper of a new thread, started in
- * {@link FileUploader#onCreate()}.
- */
- private static class ServiceHandler extends Handler {
- // don't make it a final class, and don't remove the static ; lint will
- // warn about a possible memory leak
- FileUploader mService;
-
- public ServiceHandler(Looper looper, FileUploader service) {
- super(looper);
- if (service == null)
- throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
- mService = service;
- }
-
- @Override
- public void handleMessage(Message msg) {
- @SuppressWarnings("unchecked")
- AbstractList requestedUploads = (AbstractList) msg.obj;
- if (msg.obj != null) {
- Iterator it = requestedUploads.iterator();
- while (it.hasNext()) {
- mService.uploadFile(it.next());
- }
- }
- Log_OC.d(TAG, "Stopping command after id " + msg.arg1);
- mService.stopSelf(msg.arg1);
- }
- }
-
- /**
- * Core upload method: sends the file(s) to upload
- *
- * @param uploadKey Key to access the upload to perform, contained in
- * mPendingUploads
- */
- public void uploadFile(String uploadKey) {
-
- synchronized (mPendingUploads) {
- mCurrentUpload = mPendingUploads.get(uploadKey);
- }
-
- if (mCurrentUpload != null) {
-
- // Detect if the account exists
- if (AccountUtils.exists(mCurrentUpload.getAccount(), getApplicationContext())) {
- Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().name + " exists");
-
- notifyUploadStart(mCurrentUpload);
-
- RemoteOperationResult uploadResult = null, grantResult;
-
- try {
- /// prepare client object to send requests to the ownCloud server
- if (mUploadClient == null ||
- !mLastAccount.equals(mCurrentUpload.getAccount())) {
- mLastAccount = mCurrentUpload.getAccount();
- mStorageManager =
- new FileDataStorageManager(mLastAccount, getContentResolver());
- OwnCloudAccount ocAccount = new OwnCloudAccount(mLastAccount, this);
- mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().
- getClientFor(ocAccount, this);
- }
-
- /// check the existence of the parent folder for the file to upload
- String remoteParentPath = new File(mCurrentUpload.getRemotePath()).getParent();
- remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ?
- remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
- grantResult = grantFolderExistence(remoteParentPath);
-
- /// perform the upload
- if (grantResult.isSuccess()) {
- OCFile parent = mStorageManager.getFileByPath(remoteParentPath);
- mCurrentUpload.getFile().setParentId(parent.getFileId());
- uploadResult = mCurrentUpload.execute(mUploadClient);
- if (uploadResult.isSuccess()) {
- saveUploadedFile();
- }
- } else {
- uploadResult = grantResult;
- }
-
- } catch (AccountsException e) {
- Log_OC.e(TAG, "Error while trying to get autorization for " +
- mLastAccount.name, e);
- uploadResult = new RemoteOperationResult(e);
-
- } catch (IOException e) {
- Log_OC.e(TAG, "Error while trying to get autorization for " +
- mLastAccount.name, e);
- uploadResult = new RemoteOperationResult(e);
-
- } finally {
- synchronized (mPendingUploads) {
- mPendingUploads.remove(uploadKey);
- Log_OC.i(TAG, "Remove CurrentUploadItem from pending upload Item Map.");
- }
- if (uploadResult != null && uploadResult.isException()) {
- // enforce the creation of a new client object for next uploads;
- // this grant that a new socket will be created in the future if
- // the current exception is due to an abrupt lose of network connection
- mUploadClient = null;
- }
- }
-
- /// notify result
- notifyUploadResult(uploadResult, mCurrentUpload);
- sendFinalBroadcast(mCurrentUpload, uploadResult);
-
- } else {
- // Cancel the transfer
- Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().toString() +
- " doesn't exist");
- cancelUploadForAccount(mCurrentUpload.getAccount().name);
-
- }
- }
-
- }
-
- /**
- * Checks the existence of the folder where the current file will be uploaded both
- * in the remote server and in the local database.
- *
- * If the upload is set to enforce the creation of the folder, the method tries to
- * create it both remote and locally.
- *
- * @param pathToGrant Full remote path whose existence will be granted.
- * @return An {@link OCFile} instance corresponding to the folder where the file
- * will be uploaded.
- */
- private RemoteOperationResult grantFolderExistence(String pathToGrant) {
- RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, this, false);
- RemoteOperationResult result = operation.execute(mUploadClient);
- if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND &&
- mCurrentUpload.isRemoteFolderToBeCreated()) {
- SyncOperation syncOp = new CreateFolderOperation( pathToGrant, true);
- result = syncOp.execute(mUploadClient, mStorageManager);
- }
- if (result.isSuccess()) {
- OCFile parentDir = mStorageManager.getFileByPath(pathToGrant);
- if (parentDir == null) {
- parentDir = createLocalFolder(pathToGrant);
- }
- if (parentDir != null) {
- result = new RemoteOperationResult(ResultCode.OK);
- } else {
- result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
- }
- }
- return result;
- }
-
-
- private OCFile createLocalFolder(String remotePath) {
- String parentPath = new File(remotePath).getParent();
- parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ?
- parentPath : parentPath + OCFile.PATH_SEPARATOR;
- OCFile parent = mStorageManager.getFileByPath(parentPath);
- if (parent == null) {
- parent = createLocalFolder(parentPath);
- }
- if (parent != null) {
- OCFile createdFolder = new OCFile(remotePath);
- createdFolder.setMimetype("DIR");
- createdFolder.setParentId(parent.getFileId());
- mStorageManager.saveFile(createdFolder);
- return createdFolder;
- }
- return null;
- }
-
-
- /**
- * Saves a OC File after a successful upload.
- *
- * A PROPFIND is necessary to keep the props in the local database
- * synchronized with the server, specially the modification time and Etag
- * (where available)
- *
- * TODO refactor this ugly thing
- */
- private void saveUploadedFile() {
- OCFile file = mCurrentUpload.getFile();
- if (file.fileExists()) {
- file = mStorageManager.getFileById(file.getFileId());
- }
- long syncDate = System.currentTimeMillis();
- file.setLastSyncDateForData(syncDate);
-
- // new PROPFIND to keep data consistent with server
- // in theory, should return the same we already have
- ReadRemoteFileOperation operation =
- new ReadRemoteFileOperation(mCurrentUpload.getRemotePath());
- RemoteOperationResult result = operation.execute(mUploadClient);
- if (result.isSuccess()) {
- updateOCFile(file, (RemoteFile) result.getData().get(0));
- file.setLastSyncDateForProperties(syncDate);
- }
-
- // / maybe this would be better as part of UploadFileOperation... or
- // maybe all this method
- if (mCurrentUpload.wasRenamed()) {
- OCFile oldFile = mCurrentUpload.getOldFile();
- if (oldFile.fileExists()) {
- oldFile.setStoragePath(null);
- mStorageManager.saveFile(oldFile);
-
- } // else: it was just an automatic renaming due to a name
- // coincidence; nothing else is needed, the storagePath is right
- // in the instance returned by mCurrentUpload.getFile()
- }
- file.setNeedsUpdateThumbnail(true);
- mStorageManager.saveFile(file);
- }
-
- private void updateOCFile(OCFile file, RemoteFile remoteFile) {
- file.setCreationTimestamp(remoteFile.getCreationTimestamp());
- file.setFileLength(remoteFile.getLength());
- file.setMimetype(remoteFile.getMimeType());
- file.setModificationTimestamp(remoteFile.getModifiedTimestamp());
- file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp());
- // file.setEtag(remoteFile.getEtag()); // TODO Etag, where available
- file.setRemoteId(remoteFile.getRemoteId());
- }
-
- private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType,
- FileDataStorageManager storageManager) {
-
- // MIME type
- if (mimeType == null || mimeType.length() <= 0) {
- try {
- mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
- remotePath.substring(remotePath.lastIndexOf('.') + 1));
- } catch (IndexOutOfBoundsException e) {
- Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " +
- remotePath);
- }
- }
- if (mimeType == null) {
- mimeType = "application/octet-stream";
- }
-
- if (isPdfFileFromContentProviderWithoutExtension(localPath, mimeType)){
- remotePath += FILE_EXTENSION_PDF;
- }
-
- OCFile newFile = new OCFile(remotePath);
- newFile.setStoragePath(localPath);
- newFile.setLastSyncDateForProperties(0);
- newFile.setLastSyncDateForData(0);
-
- // size
- if (localPath != null && localPath.length() > 0) {
- File localFile = new File(localPath);
- newFile.setFileLength(localFile.length());
- newFile.setLastSyncDateForData(localFile.lastModified());
- } // don't worry about not assigning size, the problems with localPath
- // are checked when the UploadFileOperation instance is created
-
-
- newFile.setMimetype(mimeType);
-
- return newFile;
- }
-
- /**
- * Creates a status notification to show the upload progress
- *
- * @param upload Upload operation starting.
- */
- private void notifyUploadStart(UploadFileOperation upload) {
- // / create status notification with a progress bar
- mLastPercent = 0;
- mNotificationBuilder =
- NotificationBuilderWithProgressBar.newNotificationBuilderWithProgressBar(this);
- mNotificationBuilder
- .setOngoing(true)
- .setSmallIcon(R.drawable.notification_icon)
- .setTicker(getString(R.string.uploader_upload_in_progress_ticker))
- .setContentTitle(getString(R.string.uploader_upload_in_progress_ticker))
- .setProgress(100, 0, false)
- .setContentText(
- String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName()));
-
- /// includes a pending intent in the notification showing the details view of the file
- Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class);
- showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, upload.getFile());
- showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, upload.getAccount());
- showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mNotificationBuilder.setContentIntent(PendingIntent.getActivity(
- this, (int) System.currentTimeMillis(), showDetailsIntent, 0
- ));
-
- mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build());
- }
-
- /**
- * Callback method to update the progress bar in the status notification
- */
- @Override
- public void onTransferProgress(long progressRate, long totalTransferredSoFar,
- long totalToTransfer, String filePath) {
- int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
- if (percent != mLastPercent) {
- mNotificationBuilder.setProgress(100, percent, false);
- String fileName = filePath.substring(
- filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1);
- String text = String.format(getString(R.string.uploader_upload_in_progress_content), percent, fileName);
- mNotificationBuilder.setContentText(text);
- mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build());
- }
- mLastPercent = percent;
- }
-
- /**
- * Updates the status notification with the result of an upload operation.
- *
- * @param uploadResult Result of the upload operation.
- * @param upload Finished upload operation
- */
- private void notifyUploadResult(
- RemoteOperationResult uploadResult, UploadFileOperation upload) {
- Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.getCode());
- // / cancelled operation or success -> silent removal of progress notification
- mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker);
-
- // Show the result: success or fail notification
- if (!uploadResult.isCancelled()) {
- int tickerId = (uploadResult.isSuccess()) ? R.string.uploader_upload_succeeded_ticker :
- R.string.uploader_upload_failed_ticker;
-
- String content;
-
- // check credentials error
- boolean needsToUpdateCredentials = (
- uploadResult.getCode() == ResultCode.UNAUTHORIZED ||
- uploadResult.isIdPRedirection()
- );
- tickerId = (needsToUpdateCredentials) ?
- R.string.uploader_upload_failed_credentials_error : tickerId;
-
- mNotificationBuilder
- .setTicker(getString(tickerId))
- .setContentTitle(getString(tickerId))
- .setAutoCancel(true)
- .setOngoing(false)
- .setProgress(0, 0, false);
-
- content = ErrorMessageAdapter.getErrorCauseMessage(
- uploadResult, upload, getResources()
- );
-
- if (needsToUpdateCredentials) {
- // let the user update credentials with one click
- Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
- updateAccountCredentials.putExtra(
- AuthenticatorActivity.EXTRA_ACCOUNT, upload.getAccount()
- );
- updateAccountCredentials.putExtra(
- AuthenticatorActivity.EXTRA_ACTION,
- AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
- );
- updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);
- mNotificationBuilder.setContentIntent(PendingIntent.getActivity(
- this,
- (int) System.currentTimeMillis(),
- updateAccountCredentials,
- PendingIntent.FLAG_ONE_SHOT
- ));
-
- mUploadClient = null;
- // grant that future retries on the same account will get the fresh credentials
- } else {
- mNotificationBuilder.setContentText(content);
-
- if (upload.isInstant()) {
- DbHandler db = null;
- try {
- db = new DbHandler(this.getBaseContext());
- String message = uploadResult.getLogMessage() + " errorCode: " +
- uploadResult.getCode();
- Log_OC.e(TAG, message + " Http-Code: " + uploadResult.getHttpCode());
- if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) {
- //message = getString(R.string.failed_upload_quota_exceeded_text);
- if (db.updateFileState(
- upload.getOriginalStoragePath(),
- DbHandler.UPLOAD_STATUS_UPLOAD_FAILED,
- message) == 0) {
- db.putFileForLater(
- upload.getOriginalStoragePath(),
- upload.getAccount().name,
- message
- );
- }
- }
- } finally {
- if (db != null) {
- db.close();
- }
- }
- }
- }
-
- mNotificationBuilder.setContentText(content);
- mNotificationManager.notify(tickerId, mNotificationBuilder.build());
-
- if (uploadResult.isSuccess()) {
-
- DbHandler db = new DbHandler(this.getBaseContext());
- db.removeIUPendingFile(mCurrentUpload.getOriginalStoragePath());
- db.close();
-
- // remove success notification, with a delay of 2 seconds
- NotificationDelayer.cancelWithDelay(
- mNotificationManager,
- R.string.uploader_upload_succeeded_ticker,
- 2000);
-
- }
- }
- }
-
- /**
- * Sends a broadcast in order to the interested activities can update their
- * view
- *
- * @param upload Finished upload operation
- * @param uploadResult Result of the upload operation
- */
- private void sendFinalBroadcast(UploadFileOperation upload, RemoteOperationResult uploadResult) {
- Intent end = new Intent(getUploadFinishMessage());
- end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote
- // path, after
- // possible
- // automatic
- // renaming
- if (upload.wasRenamed()) {
- end.putExtra(EXTRA_OLD_REMOTE_PATH, upload.getOldFile().getRemotePath());
- }
- end.putExtra(EXTRA_OLD_FILE_PATH, upload.getOriginalStoragePath());
- end.putExtra(ACCOUNT_NAME, upload.getAccount().name);
- end.putExtra(EXTRA_UPLOAD_RESULT, uploadResult.isSuccess());
- sendStickyBroadcast(end);
- }
-
- /**
- * Checks if content provider, using the content:// scheme, returns a file with mime-type
- * 'application/pdf' but file has not extension
- * @param localPath Full path to a file in the local file system.
- * @param mimeType MIME type of the file.
- * @return true if is needed to add the pdf file extension to the file
- */
- private boolean isPdfFileFromContentProviderWithoutExtension(String localPath,
- String mimeType) {
- return localPath.startsWith(UriUtils.URI_CONTENT_SCHEME) &&
- mimeType.equals(MIME_TYPE_PDF) &&
- !localPath.endsWith(FILE_EXTENSION_PDF);
- }
-
- /**
- * Remove uploads of an account
- * @param accountName Name of an OC account
- */
- private void cancelUploadForAccount(String accountName){
- // this can be slow if there are many uploads :(
- Iterator it = mPendingUploads.keySet().iterator();
- Log_OC.d(TAG, "Number of pending updloads= " + mPendingUploads.size());
- while (it.hasNext()) {
- String key = it.next();
- Log_OC.d(TAG, "mPendingUploads CANCELLED " + key);
- if (key.startsWith(accountName)) {
- synchronized (mPendingUploads) {
- mPendingUploads.remove(key);
- }
- }
- }
- }
-}
diff --git a/src/com/owncloud/android/media/MediaService.java b/src/com/owncloud/android/media/MediaService.java
index e53c635fcc0..cd575dc2b8a 100644
--- a/src/com/owncloud/android/media/MediaService.java
+++ b/src/com/owncloud/android/media/MediaService.java
@@ -35,6 +35,7 @@
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
import android.os.IBinder;
+import android.os.Parcelable;
import android.os.PowerManager;
import android.widget.Toast;
@@ -536,7 +537,7 @@ public void onPrepared(MediaPlayer player) {
private void updateNotification(String content) {
// TODO check if updating the Intent is really necessary
Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class);
- showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, mFile);
+ showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable)mFile);
showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount);
showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(),
@@ -573,7 +574,7 @@ private void setUpAsForeground(String content) {
/// includes a pending intent in the notification showing the details view of the file
Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class);
- showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, mFile);
+ showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable)mFile);
showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount);
showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(),
diff --git a/src/com/owncloud/android/media/MediaServiceBinder.java b/src/com/owncloud/android/media/MediaServiceBinder.java
index 95bf5207393..654c6599ca1 100644
--- a/src/com/owncloud/android/media/MediaServiceBinder.java
+++ b/src/com/owncloud/android/media/MediaServiceBinder.java
@@ -29,6 +29,7 @@
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Binder;
+import android.os.Parcelable;
import android.widget.MediaController;
@@ -153,7 +154,7 @@ public void start(Account account, OCFile file, boolean playImmediately, int pos
Log_OC.d(TAG, "Loading and starting through binder...");
Intent i = new Intent(mService, MediaService.class);
i.putExtra(MediaService.EXTRA_ACCOUNT, account);
- i.putExtra(MediaService.EXTRA_FILE, file);
+ i.putExtra(MediaService.EXTRA_FILE, (Parcelable)file);
i.putExtra(MediaService.EXTRA_PLAY_ON_LOAD, playImmediately);
i.putExtra(MediaService.EXTRA_START_POSITION, position);
i.setAction(MediaService.ACTION_PLAY_FILE);
diff --git a/src/com/owncloud/android/operations/SynchronizeFileOperation.java b/src/com/owncloud/android/operations/SynchronizeFileOperation.java
index f94adb15bd5..4221b2ee1bf 100644
--- a/src/com/owncloud/android/operations/SynchronizeFileOperation.java
+++ b/src/com/owncloud/android/operations/SynchronizeFileOperation.java
@@ -25,7 +25,7 @@
import com.owncloud.android.MainApp;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileDownloader;
-import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.files.services.FileUploadService;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.resources.files.RemoteFile;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
@@ -38,6 +38,7 @@
import android.accounts.Account;
import android.content.Context;
import android.content.Intent;
+import android.os.Parcelable;
/**
* Remote operation performing the read of remote file in the ownCloud server.
@@ -281,15 +282,16 @@ protected RemoteOperationResult run(OwnCloudClient client) {
* @param file OCFile object representing the file to upload
*/
private void requestForUpload(OCFile file) {
- Intent i = new Intent(mContext, FileUploader.class);
- i.putExtra(FileUploader.KEY_ACCOUNT, mAccount);
- i.putExtra(FileUploader.KEY_FILE, file);
+ Intent i = new Intent(mContext, FileUploadService.class);
+ i.putExtra(FileUploadService.KEY_ACCOUNT, mAccount);
+ i.putExtra(FileUploadService.KEY_FILE, (Parcelable)file);
/*i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath);
- // doing this we would lose the value of isFavorite in the road, and maybe
- // it's not updated in the database when the FileUploader service gets it!
+ // doing this we would lose the value of keepInSync in the road, and maybe it's not updated
+ in the database when the FileUploader service gets it!
i.putExtra(FileUploader.KEY_LOCAL_FILE, localFile.getStoragePath());*/
- i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
- i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true);
+ i.putExtra(FileUploadService.KEY_UPLOAD_TYPE,
+ FileUploadService.UploadSingleMulti.UPLOAD_SINGLE_FILE);
+ i.putExtra(FileUploadService.KEY_FORCE_OVERWRITE, true);
mContext.startService(i);
mTransferWasRequested = true;
}
@@ -303,7 +305,7 @@ private void requestForUpload(OCFile file) {
private void requestForDownload(OCFile file) {
Intent i = new Intent(mContext, FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
- i.putExtra(FileDownloader.EXTRA_FILE, file);
+ i.putExtra(FileDownloader.EXTRA_FILE, (Parcelable)file);
mContext.startService(i);
mTransferWasRequested = true;
}
diff --git a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java
index 78b6a7baacb..87cd2cc0679 100644
--- a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java
+++ b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java
@@ -30,6 +30,7 @@
import android.accounts.Account;
import android.content.Context;
import android.content.Intent;
+import android.os.Parcelable;
import android.util.Log;
@@ -432,7 +433,7 @@ private void startDirectDownloads() throws OperationCancelledException {
}
Intent i = new Intent(mContext, FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
- i.putExtra(FileDownloader.EXTRA_FILE, file);
+ i.putExtra(FileDownloader.EXTRA_FILE, (Parcelable) file);
mContext.startService(i);
}
}
@@ -486,15 +487,7 @@ private void startContentSynchronizations(List filesToSyncContent
* @return New OCFile instance representing the remote resource described by we.
*/
private OCFile fillOCFile(RemoteFile remote) {
- OCFile file = new OCFile(remote.getRemotePath());
- file.setCreationTimestamp(remote.getCreationTimestamp());
- file.setFileLength(remote.getLength());
- file.setMimetype(remote.getMimeType());
- file.setModificationTimestamp(remote.getModifiedTimestamp());
- file.setEtag(remote.getEtag());
- file.setPermissions(remote.getPermissions());
- file.setRemoteId(remote.getRemoteId());
- return file;
+ return FileStorageUtils.fillOCFile(remote);
}
diff --git a/src/com/owncloud/android/operations/UploadFileOperation.java b/src/com/owncloud/android/operations/UploadFileOperation.java
index 12cf1ac74c1..e907d6ad9f0 100644
--- a/src/com/owncloud/android/operations/UploadFileOperation.java
+++ b/src/com/owncloud/android/operations/UploadFileOperation.java
@@ -40,7 +40,10 @@
import com.owncloud.android.MainApp;
import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.files.services.FileUploadService;
+import com.owncloud.android.files.services.FileUploadService.LocalBehaviour;
+import com.owncloud.android.lib.common.network.ProgressiveDataTransferer;
+import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
import com.owncloud.android.lib.common.network.ProgressiveDataTransferer;
@@ -64,33 +67,43 @@ public class UploadFileOperation extends RemoteOperation {
private static final String TAG = UploadFileOperation.class.getSimpleName();
private Account mAccount;
+ /**
+ * OCFile which is to be uploaded.
+ */
private OCFile mFile;
+ /**
+ * Original OCFile which is to be uploaded in case file had to be renamed
+ * (if forceOverwrite==false and remote file already exists).
+ */
private OCFile mOldFile;
private String mRemotePath = null;
private boolean mChunked = false;
- private boolean mIsInstant = false;
private boolean mRemoteFolderToBeCreated = false;
private boolean mForceOverwrite = false;
- private int mLocalBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY;
+ private LocalBehaviour mLocalBehaviour = FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_COPY;
private boolean mWasRenamed = false;
private String mOriginalFileName = null;
+ /**
+ * Local path to file which is to be uploaded (before any possible renaming or moving).
+ */
private String mOriginalStoragePath = null;
PutMethod mPutMethod = null;
private Set mDataTransferListeners = new HashSet();
- private AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
+
+ private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
+ private final AtomicBoolean mUploadStarted = new AtomicBoolean(false);
+
private Context mContext;
private UploadRemoteFileOperation mUploadOperation;
protected RequestEntity mEntity = null;
-
public UploadFileOperation( Account account,
OCFile file,
boolean chunked,
- boolean isInstant,
boolean forceOverwrite,
- int localBehaviour,
+ LocalBehaviour localBehaviour,
Context context) {
if (account == null)
throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation " +
@@ -107,7 +120,6 @@ public UploadFileOperation( Account account,
mFile = file;
mRemotePath = file.getRemotePath();
mChunked = chunked;
- mIsInstant = isInstant;
mForceOverwrite = forceOverwrite;
mLocalBehaviour = localBehaviour;
mOriginalStoragePath = mFile.getStoragePath();
@@ -127,6 +139,10 @@ public OCFile getFile() {
return mFile;
}
+ /**
+ * If remote file was renamed, return original OCFile which was uploaded. Is
+ * null is file was not renamed.
+ */
public OCFile getOldFile() {
return mOldFile;
}
@@ -147,10 +163,6 @@ public String getMimeType() {
return mFile.getMimetype();
}
- public boolean isInstant() {
- return mIsInstant;
- }
-
public boolean isRemoteFolderToBeCreated() {
return mRemoteFolderToBeCreated;
}
@@ -178,6 +190,9 @@ public void addDatatransferProgressListener (OnDatatransferProgressListener list
if (mEntity != null) {
((ProgressiveDataTransferer)mEntity).addDatatransferProgressListener(listener);
}
+ if(mUploadOperation != null){
+ mUploadOperation.addDatatransferProgressListener(listener);
+ }
}
public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
@@ -187,10 +202,15 @@ public void removeDatatransferProgressListener(OnDatatransferProgressListener li
if (mEntity != null) {
((ProgressiveDataTransferer)mEntity).removeDatatransferProgressListener(listener);
}
+ if(mUploadOperation != null){
+ mUploadOperation.removeDatatransferProgressListener(listener);
+ }
}
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
+ mCancellationRequested.set(false);
+ mUploadStarted.set(true);
RemoteOperationResult result = null;
boolean localCopyPassed = false, nameCheckPassed = false;
File temporalFile = null, originalFile = new File(mOriginalStoragePath), expectedFile = null;
@@ -211,11 +231,16 @@ protected RemoteOperationResult run(OwnCloudClient client) {
// getAvailableRemotePath()
// !!!
expectedFile = new File(expectedPath);
+
+ if (mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
// check location of local file; if not the expected, copy to a
// temporal file before upload (if COPY is the expected behaviour)
if (!mOriginalStoragePath.equals(expectedPath) &&
- mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY) {
+ mLocalBehaviour == FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_COPY) {
+
if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) {
result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL);
@@ -232,11 +257,13 @@ protected RemoteOperationResult run(OwnCloudClient client) {
File temporalParent = temporalFile.getParentFile();
temporalParent.mkdirs();
if (!temporalParent.isDirectory()) {
- throw new IOException("Unexpected error: parent directory could not be created");
+ throw new IOException(
+ "Unexpected error: parent directory could not be created");
}
temporalFile.createNewFile();
if (!temporalFile.isFile()) {
- throw new IOException("Unexpected error: target file could not be created");
+ throw new IOException(
+ "Unexpected error: target file could not be created");
}
InputStream in = null;
@@ -306,6 +333,10 @@ protected RemoteOperationResult run(OwnCloudClient client) {
}
}
localCopyPassed = (result == null);
+
+ if (mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
/// perform the upload
if ( mChunked &&
@@ -321,40 +352,38 @@ protected RemoteOperationResult run(OwnCloudClient client) {
while (listener.hasNext()) {
mUploadOperation.addDatatransferProgressListener(listener.next());
}
- if (!mCancellationRequested.get()) {
- result = mUploadOperation.execute(client);
+ result = mUploadOperation.execute(client);
- /// move local temporal file or original file to its corresponding
- // location in the ownCloud local folder
- if (result.isSuccess()) {
- if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_FORGET) {
- mFile.setStoragePath(null);
+ /// move local temporal file or original file to its corresponding
+ // location in the ownCloud local folder
+ if (result.isSuccess()) {
+ if (mLocalBehaviour == FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_FORGET) {
+ mFile.setStoragePath(null);
- } else {
- mFile.setStoragePath(expectedPath);
- File fileToMove = null;
- if (temporalFile != null) { // FileUploader.LOCAL_BEHAVIOUR_COPY
- // ; see where temporalFile was
- // set
- fileToMove = temporalFile;
- } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE
- fileToMove = originalFile;
- }
- if (!expectedFile.equals(fileToMove)) {
- File expectedFolder = expectedFile.getParentFile();
- expectedFolder.mkdirs();
- if (!expectedFolder.isDirectory() || !fileToMove.renameTo(expectedFile)) {
- mFile.setStoragePath(null); // forget the local file
- // by now, treat this as a success; the file was
- // uploaded; the user won't like that the local file
- // is not linked, but this should be a very rare
- // fail;
- // the best option could be show a warning message
- // (but not a fail)
- // result = new
- // RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED);
- // return result;
- }
+ } else {
+ mFile.setStoragePath(expectedPath);
+ File fileToMove = null;
+ if (temporalFile != null) { // FileUploader.LOCAL_BEHAVIOUR_COPY
+ // ; see where temporalFile was
+ // set
+ fileToMove = temporalFile;
+ } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE
+ fileToMove = originalFile;
+ }
+ if (!expectedFile.equals(fileToMove)) {
+ File expectedFolder = expectedFile.getParentFile();
+ expectedFolder.mkdirs();
+ if (!expectedFolder.isDirectory() || !fileToMove.renameTo(expectedFile)) {
+ mFile.setStoragePath(null); // forget the local file
+ // by now, treat this as a success; the file was
+ // uploaded; the user won't like that the local file
+ // is not linked, but this should be a very rare
+ // fail;
+ // the best option could be show a warning message
+ // (but not a fail)
+ // result = new
+ // RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_MOVED);
+ // return result;
}
}
}
@@ -369,6 +398,7 @@ protected RemoteOperationResult run(OwnCloudClient client) {
}
} finally {
+ mUploadStarted.set(false);
if (temporalFile != null && !originalFile.equals(temporalFile)) {
temporalFile.delete();
}
@@ -385,8 +415,14 @@ protected RemoteOperationResult run(OwnCloudClient client) {
FileStorageUtils.getSavePath(mAccount.name)
+ ")";
}
- Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
- ": " + result.getLogMessage() + complement, result.getException());
+ if(result.isCancelled()){
+ Log_OC.w(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
+ ": " + result.getLogMessage());
+ } else {
+ Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
+ ": " + result.getLogMessage() + complement, result.getException());
+ }
+
} else {
Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
": " + result.getLogMessage());
@@ -397,6 +433,11 @@ protected RemoteOperationResult run(OwnCloudClient client) {
return result;
}
+ /**
+ * Create a new OCFile mFile with new remote path. This is required if forceOverwrite==false.
+ * New file is stored as mFile, original as mOldFile.
+ * @param newRemotePath new remote path
+ */
private void createNewOCFile(String newRemotePath) {
// a new OCFile instance must be created for a new remote path
OCFile newFile = new OCFile(newRemotePath);
@@ -463,10 +504,30 @@ private boolean existsFile(OwnCloudClient client, String remotePath){
return result.isSuccess();
}
+ /**
+ * Allows to cancel the actual upload operation. If actual upload operating
+ * is in progress it is cancelled, if upload preparation is being performed
+ * upload will not take place.
+ */
public void cancel() {
- mCancellationRequested = new AtomicBoolean(true);
- if (mUploadOperation != null) {
+ if (mUploadOperation == null) {
+ if (mUploadStarted.get()) {
+ Log_OC.d(TAG, "Cancelling upload during upload preparations.");
+ mCancellationRequested.set(true);
+ } else {
+ Log_OC.e(TAG, "No upload in progress. This should not happen.");
+ }
+ } else {
+ Log_OC.d(TAG, "Cancelling upload during actual upload operation.");
mUploadOperation.cancel();
}
}
+
+ /**
+ * As soon as this method return true, upload can be cancel via cancel().
+ */
+ public boolean isUploadInProgress() {
+ return mUploadStarted.get();
+
+ }
}
diff --git a/src/com/owncloud/android/services/observer/FileObserverService.java b/src/com/owncloud/android/services/observer/FileObserverService.java
index 83de450bc31..c4a7ef8cffa 100644
--- a/src/com/owncloud/android/services/observer/FileObserverService.java
+++ b/src/com/owncloud/android/services/observer/FileObserverService.java
@@ -34,6 +34,7 @@
import android.content.IntentFilter;
import android.database.Cursor;
import android.os.IBinder;
+import android.os.Parcelable;
import com.owncloud.android.MainApp;
import com.owncloud.android.authentication.AccountUtils;
@@ -102,7 +103,7 @@ public static Intent makeObservedFileIntent(
Intent intent = new Intent(context, FileObserverService.class);
intent.setAction(watchIt ? FileObserverService.ACTION_ADD_OBSERVED_FILE
: FileObserverService.ACTION_DEL_OBSERVED_FILE);
- intent.putExtra(FileObserverService.ARG_FILE, file);
+ intent.putExtra(FileObserverService.ARG_FILE, (Parcelable)file);
intent.putExtra(FileObserverService.ARG_ACCOUNT, account);
return intent;
}
diff --git a/src/com/owncloud/android/services/observer/FolderObserver.java b/src/com/owncloud/android/services/observer/FolderObserver.java
index 5329b526ae4..44798ef10dd 100644
--- a/src/com/owncloud/android/services/observer/FolderObserver.java
+++ b/src/com/owncloud/android/services/observer/FolderObserver.java
@@ -28,6 +28,7 @@
import android.content.Context;
import android.content.Intent;
import android.os.FileObserver;
+import android.os.Parcelable;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
@@ -204,7 +205,7 @@ private void startSyncOperation(String fileName) {
// this can be very intrusive; a notification should be preferred
Intent i = new Intent(mContext, ConflictsResolveActivity.class);
i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
- i.putExtra(ConflictsResolveActivity.EXTRA_FILE, file);
+ i.putExtra(ConflictsResolveActivity.EXTRA_FILE, (Parcelable)file);
i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mAccount);
mContext.startActivity(i);
}
diff --git a/src/com/owncloud/android/ui/activity/ComponentsGetter.java b/src/com/owncloud/android/ui/activity/ComponentsGetter.java
index 043f67e1200..dc9b6c0e5ee 100644
--- a/src/com/owncloud/android/ui/activity/ComponentsGetter.java
+++ b/src/com/owncloud/android/ui/activity/ComponentsGetter.java
@@ -23,25 +23,28 @@
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.files.FileOperationsHelper;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder;
import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
public interface ComponentsGetter {
/**
- * To be invoked when the parent activity is fully created to get a reference to the FileDownloader service API.
+ * To be invoked when the parent activity is fully created to get a reference
+ * to the FileDownloader service API.
*/
public FileDownloaderBinder getFileDownloaderBinder();
/**
- * To be invoked when the parent activity is fully created to get a reference to the FileUploader service API.
+ * To be invoked when the parent activity is fully created to get a reference
+ * to the FileUploader service API.
*/
public FileUploaderBinder getFileUploaderBinder();
/**
- * To be invoked when the parent activity is fully created to get a reference to the OperationsSerivce service API.
+ * To be invoked when the parent activity is fully created to get a reference
+ * to the OperationsSerivce service API.
*/
public OperationsServiceBinder getOperationsServiceBinder();
diff --git a/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java
index 9b7050e80da..619e0368fd0 100644
--- a/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java
+++ b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java
@@ -24,15 +24,17 @@
import android.content.Intent;
import android.os.Bundle;
+import android.os.Parcelable;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileDownloader;
-import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.files.services.FileUploadService;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.ui.dialog.ConflictsResolveDialog;
import com.owncloud.android.ui.dialog.ConflictsResolveDialog.Decision;
import com.owncloud.android.ui.dialog.ConflictsResolveDialog.OnConflictDecisionMadeListener;
+
/**
* Wrapper activity which will be launched if keep-in-sync file will be modified by external
* application.
@@ -48,7 +50,7 @@ protected void onCreate(Bundle savedInstanceState) {
@Override
public void conflictDecisionMade(Decision decision) {
- Intent i = new Intent(getApplicationContext(), FileUploader.class);
+ Intent i = new Intent(getApplicationContext(), FileUploadService.class);
switch (decision) {
case CANCEL:
@@ -56,16 +58,16 @@ public void conflictDecisionMade(Decision decision) {
return;
case OVERWRITE:
// use local version -> overwrite on server
- i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true);
+ i.putExtra(FileUploadService.KEY_FORCE_OVERWRITE, true);
break;
case KEEP_BOTH:
- i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);
+ i.putExtra(FileUploadService.KEY_LOCAL_BEHAVIOUR, FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_MOVE);
break;
case SERVER:
// use server version -> delete local, request download
Intent intent = new Intent(this, FileDownloader.class);
intent.putExtra(FileDownloader.EXTRA_ACCOUNT, getAccount());
- intent.putExtra(FileDownloader.EXTRA_FILE, getFile());
+ intent.putExtra(FileDownloader.EXTRA_FILE, (Parcelable) getFile());
startService(intent);
finish();
return;
@@ -73,9 +75,9 @@ public void conflictDecisionMade(Decision decision) {
Log_OC.wtf(TAG, "Unhandled conflict decision " + decision);
return;
}
- i.putExtra(FileUploader.KEY_ACCOUNT, getAccount());
- i.putExtra(FileUploader.KEY_FILE, getFile());
- i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
+ i.putExtra(FileUploadService.KEY_ACCOUNT, getAccount());
+ i.putExtra(FileUploadService.KEY_FILE, (Parcelable)getFile());
+ i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UploadSingleMulti.UPLOAD_SINGLE_FILE);
startService(i);
finish();
diff --git a/src/com/owncloud/android/ui/activity/FileActivity.java b/src/com/owncloud/android/ui/activity/FileActivity.java
index f1508c2852c..47959d47430 100644
--- a/src/com/owncloud/android/ui/activity/FileActivity.java
+++ b/src/com/owncloud/android/ui/activity/FileActivity.java
@@ -35,6 +35,8 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Parcelable;
+
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
@@ -43,7 +45,6 @@
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
-import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
@@ -60,9 +61,9 @@
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.FileOperationsHelper;
import com.owncloud.android.files.services.FileDownloader;
+import com.owncloud.android.files.services.FileUploadService;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder;
import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
@@ -206,7 +207,7 @@ protected void onCreate(Bundle savedInstanceState) {
}
mUploadServiceConnection = newTransferenceServiceConnection();
if (mUploadServiceConnection != null) {
- bindService(new Intent(this, FileUploader.class), mUploadServiceConnection,
+ bindService(new Intent(this, FileUploadService.class), mUploadServiceConnection,
Context.BIND_AUTO_CREATE);
}
@@ -817,7 +818,7 @@ private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation
if (!result.isSuccess()) {
if (result.getCode() == ResultCode.SYNC_CONFLICT) {
Intent i = new Intent(this, ConflictsResolveActivity.class);
- i.putExtra(ConflictsResolveActivity.EXTRA_FILE, syncedFile);
+ i.putExtra(ConflictsResolveActivity.EXTRA_FILE, (Parcelable)syncedFile);
i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, getAccount());
startActivity(i);
}
diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java
index 9c1d0661363..72ae40ddb79 100644
--- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java
+++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java
@@ -43,6 +43,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.provider.OpenableColumns;
import android.support.v4.app.Fragment;
@@ -63,10 +64,11 @@
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.db.UploadDbObject;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.files.services.FileUploadService;
+import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
@@ -108,11 +110,11 @@
/**
- * Displays, what files the user has available in his ownCloud.
+ * Displays, what files the user has available in his ownCloud. This is the main view.
*/
-public class FileDisplayActivity extends HookActivity
- implements FileFragment.ContainerActivity,
+public class FileDisplayActivity extends HookActivity implements
+ FileFragment.ContainerActivity,
OnSslUntrustedCertListener, OnEnforceableRefreshListener {
private SyncBroadcastReceiver mSyncBroadcastReceiver;
@@ -129,6 +131,9 @@ public class FileDisplayActivity extends HookActivity
private static final String KEY_SYNC_IN_PROGRESS = "SYNC_IN_PROGRESS";
private static final String KEY_WAITING_TO_SEND = "WAITING_TO_SEND";
+ public static final int DIALOG_SHORT_WAIT = 0;
+ private static final int DIALOG_CHOOSE_UPLOAD_SOURCE = 1;
+
public static final String ACTION_DETAILS = "com.owncloud.android.ui.activity.action.DETAILS";
public static final int ACTION_SELECT_CONTENT_FROM_APPS = 1;
@@ -141,7 +146,7 @@ public class FileDisplayActivity extends HookActivity
private static final String TAG_SECOND_FRAGMENT = "SECOND_FRAGMENT";
private OCFile mWaitingToPreview;
-
+
private boolean mSyncInProgress = false;
private static String DIALOG_UNTRUSTED_CERT = "DIALOG_UNTRUSTED_CERT";
@@ -164,7 +169,7 @@ protected void onCreate(Bundle savedInstanceState) {
Intent initObserversIntent = FileObserverService.makeInitIntent(this);
startService(initObserversIntent);
}
-
+
/// Load of saved instance state
if(savedInstanceState != null) {
mWaitingToPreview = (OCFile) savedInstanceState.getParcelable(
@@ -172,18 +177,18 @@ protected void onCreate(Bundle savedInstanceState) {
mSyncInProgress = savedInstanceState.getBoolean(KEY_SYNC_IN_PROGRESS);
mWaitingToSend = (OCFile) savedInstanceState.getParcelable(
FileDisplayActivity.KEY_WAITING_TO_SEND);
-
+
} else {
mWaitingToPreview = null;
mSyncInProgress = false;
mWaitingToSend = null;
- }
+ }
/// USER INTERFACE
// Inflate and set the layout view
setContentView(R.layout.files);
-
+
// Navigation Drawer
initDrawer();
@@ -210,7 +215,7 @@ protected void onCreate(Bundle savedInstanceState) {
mProgressBar.setIndeterminate(mSyncInProgress);
// always AFTER setContentView(...) ; to work around bug in its implementation
-
+
setBackgroundText();
Log_OC.v(TAG, "onCreate() end");
@@ -232,7 +237,7 @@ protected void onDestroy() {
/**
* Called when the ownCloud {@link Account} associated to the Activity was just updated.
- */
+ */
@Override
protected void onAccountSet(boolean stateWasRecovered) {
super.onAccountSet(stateWasRecovered);
@@ -276,7 +281,7 @@ protected void onAccountSet(boolean stateWasRecovered) {
if (file.isFolder()) {
startSyncFolderOperation(file, false);
}
-
+
} else {
updateFragmentsVisibility(!file.isFolder());
updateActionBarTitleAndHomeButton(file.isFolder() ? null : file);
@@ -291,11 +296,11 @@ private void createMinFragments() {
transaction.add(R.id.left_fragment_container, listOfFiles, TAG_LIST_OF_FILES);
transaction.commit();
}
-
+
private void initFragmentsWithFile() {
if (getAccount() != null && getFile() != null) {
/// First fragment
- OCFileListFragment listOfFiles = getListOfFilesFragment();
+ OCFileListFragment listOfFiles = getListOfFilesFragment();
if (listOfFiles != null) {
listOfFiles.listDirectory(getCurrentDir());
// TODO Enable when "On Device" is recovered
@@ -303,15 +308,14 @@ private void initFragmentsWithFile() {
} else {
Log_OC.e(TAG, "Still have a chance to lose the initializacion of list fragment >(");
}
-
+
/// Second fragment
- OCFile file = getFile();
+ OCFile file = getFile();
Fragment secondFragment = chooseInitialSecondFragment(file);
if (secondFragment != null) {
setSecondFragment(secondFragment);
updateFragmentsVisibility(true);
updateActionBarTitleAndHomeButton(file);
-
} else {
cleanSecondFragment();
}
@@ -330,7 +334,7 @@ private void initFragmentsWithFile() {
private Fragment chooseInitialSecondFragment(OCFile file) {
Fragment secondFragment = null;
if (file != null && !file.isFolder()) {
- if (file.isDown() && PreviewMediaFragment.canBePreviewed(file)
+ if (file.isDown() && PreviewMediaFragment.canBePreviewed(file)
&& file.getLastSyncDateForProperties() > 0 // temporal fix
) {
int startPlaybackPosition =
@@ -351,9 +355,9 @@ private Fragment chooseInitialSecondFragment(OCFile file) {
/**
* Replaces the second fragment managed by the activity with the received as
* a parameter.
- *
- * Assumes never will be more than two fragments managed at the same time.
- *
+ *
+ * Assumes never will be more than two fragments managed at the same time.
+ *
* @param fragment New second Fragment to set.
*/
private void setSecondFragment(Fragment fragment) {
@@ -440,7 +444,7 @@ protected void refreshSecondFragment(String downloadEvent, String downloadedRemo
OCFile fileInFragment = detailsFragment.getFile();
if (fileInFragment != null &&
!downloadedRemotePath.equals(fileInFragment.getRemotePath())) {
- // the user browsed to other file ; forget the automatic preview
+ // the user browsed to other file ; forget the automatic preview
mWaitingToPreview = null;
} else if (downloadEvent.equals(FileDownloader.getDownloadAddedMessage())) {
@@ -512,6 +516,12 @@ public boolean onOptionsItemSelected(MenuItem item) {
break;
}
+ case R.id.action_upload_list: {
+ Intent uploadListIntent = new Intent(getApplicationContext(),
+ UploadListActivity.class);
+ startActivity(uploadListIntent);
+ break;
+ }
case android.R.id.home: {
FileFragment second = getSecondFragment();
OCFile currentDir = getCurrentDir();
@@ -619,14 +629,14 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
} else if (requestCode == ACTION_MOVE_FILES && resultCode == RESULT_OK){
final Intent fData = data;
- final int fResultCode = resultCode;
+ final int fResultCode = resultCode;
getHandler().postDelayed(
new Runnable() {
@Override
public void run() {
requestMoveOperation(fData, fResultCode);
}
- },
+ },
DELAY_TO_REQUEST_OPERATION_ON_ACTIVITY_RESULTS
);
@@ -645,13 +655,14 @@ private void requestMultipleUpload(Intent data, int resultCode) {
remotePaths[j] = remotePathBase + (new File(filePaths[j])).getName();
}
- Intent i = new Intent(this, FileUploader.class);
- i.putExtra(FileUploader.KEY_ACCOUNT, getAccount());
- i.putExtra(FileUploader.KEY_LOCAL_FILE, filePaths);
- i.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths);
- i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES);
+ Intent i = new Intent(this, FileUploadService.class);
+ i.putExtra(FileUploadService.KEY_ACCOUNT, getAccount());
+ i.putExtra(FileUploadService.KEY_LOCAL_FILE, filePaths);
+ i.putExtra(FileUploadService.KEY_REMOTE_FILE, remotePaths);
+ i.putExtra(FileUploadService.KEY_UPLOAD_TYPE,
+ FileUploadService.UploadSingleMulti.UPLOAD_MULTIPLE_FILES);
if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)
- i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);
+ i.putExtra(FileUploadService.KEY_LOCAL_BEHAVIOUR, FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_MOVE);
startService(i);
} else {
@@ -697,8 +708,9 @@ this, getString(R.string.filedisplay_unexpected_bad_get_content),
}
}
- Intent i = new Intent(this, FileUploader.class);
- i.putExtra(FileUploader.KEY_ACCOUNT, getAccount());
+ Intent i = new Intent(this, FileUploadService.class);
+ i.putExtra(FileUploadService.KEY_ACCOUNT,
+ getAccount());
OCFile currentDir = getCurrentDir();
String remotePath = (currentDir != null) ? currentDir.getRemotePath() : OCFile.ROOT_PATH;
@@ -724,18 +736,24 @@ this, getString(R.string.filedisplay_unexpected_bad_get_content),
remotePath += new File(filePath).getName();
}
- i.putExtra(FileUploader.KEY_LOCAL_FILE, filePath);
- i.putExtra(FileUploader.KEY_REMOTE_FILE, remotePath);
- i.putExtra(FileUploader.KEY_MIME_TYPE, mimeType);
- i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
- if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)
- i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);
- startService(i);
+ i.putExtra(FileUploadService.KEY_LOCAL_FILE, filePath);
+ i.putExtra(FileUploadService.KEY_REMOTE_FILE, remotePath);
+ i.putExtra(FileUploadService.KEY_MIME_TYPE, mimeType);
+ i.putExtra(FileUploadService.KEY_UPLOAD_TYPE,
+ FileUploadService.UploadSingleMulti.UPLOAD_SINGLE_FILE);
+ if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) {
+ i.putExtra(FileUploadService.KEY_LOCAL_BEHAVIOUR,
+ FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_MOVE);
+ }
+ if(startService(i) == null) {
+ Log_OC.e(TAG, "FileUploadService could not be started");
+ }
+
}
/**
* Request the operation for moving the file/folder from one path to another
- *
+ *
* @param data Intent received
* @param resultCode Result code received
*/
@@ -782,7 +800,7 @@ protected void onSaveInstanceState(Bundle outState) {
Log_OC.v(TAG, "onSaveInstanceState() end");
}
-
+
@Override
@@ -809,7 +827,7 @@ protected void onResume() {
// syncIntentFilter);
// Listen for upload messages
- IntentFilter uploadIntentFilter = new IntentFilter(FileUploader.getUploadFinishMessage());
+ IntentFilter uploadIntentFilter = new IntentFilter(FileUploadService.getUploadFinishMessage());
mUploadFinishReceiver = new UploadFinishReceiver();
registerReceiver(mUploadFinishReceiver, uploadIntentFilter);
@@ -819,7 +837,7 @@ protected void onResume() {
downloadIntentFilter.addAction(FileDownloader.getDownloadFinishMessage());
mDownloadFinishReceiver = new DownloadFinishReceiver();
registerReceiver(mDownloadFinishReceiver, downloadIntentFilter);
-
+
Log_OC.v(TAG, "onResume() end");
}
@@ -840,7 +858,7 @@ protected void onPause() {
unregisterReceiver(mDownloadFinishReceiver);
mDownloadFinishReceiver = null;
}
-
+
super.onPause();
Log_OC.v(TAG, "onPause() end");
}
@@ -857,6 +875,7 @@ public void onReceive(Context context, Intent intent) {
String event = intent.getAction();
Log_OC.d(TAG, "Received broadcast " + event);
String accountName = intent.getStringExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME);
+
String synchFolderRemotePath =
intent.getStringExtra(FileSyncAdapter.EXTRA_FOLDER_PATH);
RemoteOperationResult synchResult =
@@ -864,12 +883,12 @@ public void onReceive(Context context, Intent intent) {
FileSyncAdapter.EXTRA_RESULT);
boolean sameAccount = (getAccount() != null &&
accountName.equals(getAccount().name) && getStorageManager() != null);
-
+
if (sameAccount) {
-
+
if (FileSyncAdapter.EVENT_FULL_SYNC_START.equals(event)) {
mSyncInProgress = true;
-
+
} else {
OCFile currentFile = (getFile() == null) ? null :
getStorageManager().getFileByPath(getFile().getRemotePath());
@@ -883,10 +902,11 @@ public void onReceive(Context context, Intent intent) {
getString(R.string.
sync_current_folder_was_removed),
synchFolderRemotePath),
+
Toast.LENGTH_LONG)
.show();
browseToRoot();
-
+
} else {
if (currentFile == null && !getFile().isFolder()) {
// currently selected file was removed in the server, and now we
@@ -907,7 +927,7 @@ public void onReceive(Context context, Intent intent) {
}
setFile(currentFile);
}
-
+
mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) &&
!RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED
.equals(event));
@@ -915,10 +935,10 @@ public void onReceive(Context context, Intent intent) {
if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
equals(event) &&
/// TODO refactor and make common
- synchResult != null && !synchResult.isSuccess() &&
- (synchResult.getCode() == ResultCode.UNAUTHORIZED ||
+ synchResult != null && !synchResult.isSuccess() &&
+ (synchResult.getCode() == ResultCode.UNAUTHORIZED ||
synchResult.isIdPRedirection() ||
- (synchResult.isException() && synchResult.getException()
+ (synchResult.isException() && synchResult.getException()
instanceof AuthenticatorException))) {
@@ -959,9 +979,9 @@ public void onReceive(Context context, Intent intent) {
/*|| mRefreshSharesInProgress*/ //);
setBackgroundText();
-
+
}
-
+
if (synchResult != null) {
if (synchResult.getCode().equals(
RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED)) {
@@ -969,13 +989,13 @@ public void onReceive(Context context, Intent intent) {
}
}
} catch (RuntimeException e) {
- // avoid app crashes after changing the serial id of RemoteOperationResult
+ // avoid app crashes after changing the serial id of RemoteOperationResult
// in owncloud library with broadcast notifications pending to process
removeStickyBroadcast(intent);
}
}
}
-
+
/**
* Show a text message on screen view for notifying user if content is
* loading or folder is empty
@@ -1007,37 +1027,40 @@ private class UploadFinishReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
try {
String uploadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
- String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME);
+ String accountName = intent.getStringExtra(FileUploadService.ACCOUNT_NAME);
boolean sameAccount = getAccount() != null && accountName.equals(getAccount().name);
OCFile currentDir = getCurrentDir();
- boolean isDescendant = (currentDir != null) && (uploadedRemotePath != null) &&
+ boolean isDescendant = (currentDir != null) && (uploadedRemotePath != null) &&
(uploadedRemotePath.startsWith(currentDir.getRemotePath()));
-
+
if (sameAccount && isDescendant) {
refreshListOfFilesFragment();
}
- boolean uploadWasFine = intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT,
+ boolean uploadWasFine = intent.getBooleanExtra(
+ FileUploadService.EXTRA_UPLOAD_RESULT,
false);
boolean renamedInUpload = getFile().getRemotePath().
- equals(intent.getStringExtra(FileUploader.EXTRA_OLD_REMOTE_PATH));
- boolean sameFile = getFile().getRemotePath().equals(uploadedRemotePath) ||
+ equals(intent.getStringExtra(FileUploadService.EXTRA_OLD_REMOTE_PATH));
+ boolean sameFile = getFile().getRemotePath().equals(uploadedRemotePath) ||
renamedInUpload;
FileFragment details = getSecondFragment();
- boolean detailFragmentIsShown = (details != null &&
+ boolean detailFragmentIsShown = (details != null &&
details instanceof FileDetailFragment);
-
+
if (sameAccount && sameFile && detailFragmentIsShown) {
if (uploadWasFine) {
setFile(getStorageManager().getFileByPath(uploadedRemotePath));
+ } else {
+ //TODO remove upload progress bar after upload failed.
}
if (renamedInUpload) {
String newName = (new File(uploadedRemotePath)).getName();
Toast msg = Toast.makeText(
- context,
+ context,
String.format(
- getString(R.string.filedetails_renamed_in_upload_msg),
- newName),
+ getString(R.string.filedetails_renamed_in_upload_msg),
+ newName),
Toast.LENGTH_LONG);
msg.show();
}
@@ -1046,7 +1069,7 @@ public void onReceive(Context context, Intent intent) {
} else {
cleanSecondFragment();
}
-
+
// Force the preview if the file is an image
if (uploadWasFine && PreviewImageFragment.canBePreviewed(getFile())) {
startImagePreview(getFile());
@@ -1054,20 +1077,21 @@ public void onReceive(Context context, Intent intent) {
}
mProgressBar.setIndeterminate(false);
+
} finally {
if (intent != null) {
removeStickyBroadcast(intent);
}
}
-
+
}
-
+
}
/**
* Class waiting for broadcast events from the {@link FileDownloader} service.
- *
+ *
* Updates the UI when a download is started or finished, provided that it is relevant for the
* current folder.
*/
@@ -1095,15 +1119,15 @@ public void onReceive(Context context, Intent intent) {
intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false)
);
}
-
+
if (mWaitingToSend != null) {
mWaitingToSend =
getStorageManager().getFileByPath(mWaitingToSend.getRemotePath());
- if (mWaitingToSend.isDown()) {
+ if (mWaitingToSend.isDown()) {
sendDownloadedFile();
}
}
-
+
} finally {
if (intent != null) {
removeStickyBroadcast(intent);
@@ -1134,10 +1158,10 @@ private boolean isSameAccount(Context context, Intent intent) {
accountName.equals(getAccount().name));
}
}
-
-
+
+
public void browseToRoot() {
- OCFileListFragment listOfFiles = getListOfFilesFragment();
+ OCFileListFragment listOfFiles = getListOfFilesFragment();
if (listOfFiles != null) { // should never be null, indeed
OCFile root = getStorageManager().getFileByPath(OCFile.ROOT_PATH);
listOfFiles.listDirectory(root);
@@ -1147,13 +1171,12 @@ public void browseToRoot() {
startSyncFolderOperation(root, false);
}
cleanSecondFragment();
-
}
/**
* {@inheritDoc}
- *
+ *
* Updates action bar and second fragment, if in dual pane mode.
*/
@Override
@@ -1165,9 +1188,9 @@ public void onBrowsedDownTo(OCFile directory) {
}
/**
- * Shows the information of the {@link OCFile} received as a
+ * Shows the information of the {@link OCFile} received as a
* parameter in the second fragment.
- *
+ *
* @param file {@link OCFile} whose details will be shown
*/
@Override
@@ -1216,7 +1239,7 @@ public void onServiceConnected(ComponentName component, IBinder service) {
}
} else if (component.equals(new ComponentName(FileDisplayActivity.this,
- FileUploader.class))) {
+ FileUploadService.class))) {
Log_OC.d(TAG, "Upload service connected");
mUploaderBinder = (FileUploaderBinder) service;
} else {
@@ -1224,7 +1247,7 @@ public void onServiceConnected(ComponentName component, IBinder service) {
}
// a new chance to get the mDownloadBinder through
// getFileDownloadBinder() - THIS IS A MESS
- OCFileListFragment listOfFiles = getListOfFilesFragment();
+ OCFileListFragment listOfFiles = getListOfFilesFragment();
if (listOfFiles != null) {
listOfFiles.listDirectory();
// TODO Enable when "On Device" is recovered ?
@@ -1245,12 +1268,12 @@ public void onServiceDisconnected(ComponentName component) {
Log_OC.d(TAG, "Download service disconnected");
mDownloaderBinder = null;
} else if (component.equals(new ComponentName(FileDisplayActivity.this,
- FileUploader.class))) {
+ FileUploadService.class))) {
Log_OC.d(TAG, "Upload service disconnected");
mUploaderBinder = null;
}
}
- };
+ };
@Override
public void onSavedCertificate() {
@@ -1274,14 +1297,14 @@ public void onCancelCertificate() {
/**
* Updates the view associated to the activity after the finish of some operation over files
* in the current account.
- *
+ *
* @param operation Removal operation performed.
* @param result Result of the removal.
*/
@Override
public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
super.onRemoteOperationFinish(operation, result);
-
+
if (operation instanceof RemoveFileOperation) {
onRemoveFileOperationFinish((RemoveFileOperation) operation, result);
@@ -1293,17 +1316,17 @@ public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationRe
} else if (operation instanceof CreateFolderOperation) {
onCreateFolderOperationFinish((CreateFolderOperation)operation, result);
-
+
} else if (operation instanceof CreateShareOperation) {
onCreateShareOperationFinish((CreateShareOperation) operation, result);
-
+
} else if (operation instanceof UnshareLinkOperation) {
onUnshareLinkOperationFinish((UnshareLinkOperation)operation, result);
-
+
} else if (operation instanceof MoveFileOperation) {
onMoveFileOperationFinish((MoveFileOperation)operation, result);
}
-
+
}
@@ -1321,34 +1344,34 @@ private void onUnshareLinkOperationFinish(UnshareLinkOperation operation,
if (result.isSuccess()) {
refreshShowDetails();
refreshListOfFilesFragment();
-
+
} else if (result.getCode() == ResultCode.SHARE_NOT_FOUND) {
cleanSecondFragment();
refreshListOfFilesFragment();
}
}
-
+
private void refreshShowDetails() {
FileFragment details = getSecondFragment();
if (details != null) {
OCFile file = details.getFile();
if (file != null) {
- file = getStorageManager().getFileByPath(file.getRemotePath());
+ file = getStorageManager().getFileByPath(file.getRemotePath());
if (details instanceof PreviewMediaFragment) {
// Refresh OCFile of the fragment
((PreviewMediaFragment) details).updateFile(file);
} else {
showDetails(file);
- }
+ }
}
invalidateOptionsMenu();
- }
+ }
}
-
+
/**
* Updates the view associated to the activity after the finish of an operation trying to
* remove a file.
- *
+ *
* @param operation Removal operation performed.
* @param result Result of the removal.
*/
@@ -1358,9 +1381,9 @@ private void onRemoveFileOperationFinish(RemoveFileOperation operation,
Toast msg = Toast.makeText(this,
ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
- Toast.LENGTH_LONG);
+ Toast.LENGTH_LONG);
msg.show();
-
+
if (result.isSuccess()) {
OCFile removedFile = operation.getFile();
FileFragment second = getSecondFragment();
@@ -1382,12 +1405,12 @@ private void onRemoveFileOperationFinish(RemoveFileOperation operation,
}
}
}
-
-
+
+
/**
- * Updates the view associated to the activity after the finish of an operation trying to move a
+ * Updates the view associated to the activity after the finish of an operation trying to move a
* file.
- *
+ *
* @param operation Move operation performed.
* @param result Result of the move operation.
*/
@@ -1399,9 +1422,9 @@ private void onMoveFileOperationFinish(MoveFileOperation operation,
} else {
dismissLoadingDialog();
try {
- Toast msg = Toast.makeText(FileDisplayActivity.this,
- ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
- Toast.LENGTH_LONG);
+ Toast msg = Toast.makeText(FileDisplayActivity.this,
+ ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
+ Toast.LENGTH_LONG);
msg.show();
} catch (NotFoundException e) {
@@ -1414,7 +1437,7 @@ private void onMoveFileOperationFinish(MoveFileOperation operation,
/**
* Updates the view associated to the activity after the finish of an operation trying to rename
* a file.
- *
+ *
* @param operation Renaming operation performed.
* @param result Result of the renaming.
*/
@@ -1449,9 +1472,9 @@ private void onRenameFileOperationFinish(RenameFileOperation operation,
} else {
Toast msg = Toast.makeText(this,
ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
- Toast.LENGTH_LONG);
+ Toast.LENGTH_LONG);
msg.show();
-
+
if (result.isSslRecoverableException()) {
mLastSslUntrustedServerResult = result;
showUntrustedCertDialog(mLastSslUntrustedServerResult);
@@ -1459,6 +1482,7 @@ private void onRenameFileOperationFinish(RenameFileOperation operation,
}
}
+
private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation,
RemoteOperationResult result) {
if (result.isSuccess()) {
@@ -1473,7 +1497,7 @@ private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation
/**
* Updates the view associated to the activity after the finish of an operation trying create a
* new folder
- *
+ *
* @param operation Creation operation performed.
* @param result Result of the creation.
*/
@@ -1485,9 +1509,9 @@ private void onCreateFolderOperationFinish(CreateFolderOperation operation,
} else {
dismissLoadingDialog();
try {
- Toast msg = Toast.makeText(FileDisplayActivity.this,
- ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
- Toast.LENGTH_LONG);
+ Toast msg = Toast.makeText(FileDisplayActivity.this,
+ ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
+ Toast.LENGTH_LONG);
msg.show();
} catch (NotFoundException e) {
@@ -1496,7 +1520,7 @@ private void onCreateFolderOperationFinish(CreateFolderOperation operation,
}
}
-
+
/**
* {@inheritDoc}
*/
@@ -1516,7 +1540,7 @@ public void onTransferStateChanged(OCFile file, boolean downloading, boolean upl
}
}
}
-
+
}
@@ -1526,7 +1550,7 @@ private void requestForDownload() {
if (!mDownloaderBinder.isDownloading(account, mWaitingToPreview)) {
Intent i = new Intent(this, FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
- i.putExtra(FileDownloader.EXTRA_FILE, mWaitingToPreview);
+ i.putExtra(FileDownloader.EXTRA_FILE, (Parcelable)mWaitingToPreview);
startService(i);
}
}
@@ -1545,12 +1569,12 @@ private OCFile getCurrentDir() {
}
return null;
}
-
+
public void startSyncFolderOperation(OCFile folder, boolean ignoreETag) {
- long currentSyncTime = System.currentTimeMillis();
-
+ long currentSyncTime = System.currentTimeMillis();
+
mSyncInProgress = true;
-
+
// perform folder synchronization
RemoteOperation synchFolderOp = new RefreshFolderOperation( folder,
currentSyncTime,
@@ -1562,13 +1586,14 @@ public void startSyncFolderOperation(OCFile folder, boolean ignoreETag) {
getApplicationContext()
);
synchFolderOp.execute(getAccount(), MainApp.getAppContext(), this, null, null);
+
mProgressBar.setIndeterminate(true);
setBackgroundText();
}
/**
- * Show untrusted cert dialog
+ * Show untrusted cert dialog
*/
public void showUntrustedCertDialog(RemoteOperationResult result) {
// Show a dialog with the certificate info
@@ -1578,28 +1603,28 @@ public void showUntrustedCertDialog(RemoteOperationResult result) {
FragmentTransaction ft = fm.beginTransaction();
dialog.show(ft, DIALOG_UNTRUSTED_CERT);
}
-
+
private void requestForDownload(OCFile file) {
Account account = getAccount();
if (!mDownloaderBinder.isDownloading(account, mWaitingToPreview)) {
Intent i = new Intent(this, FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
- i.putExtra(FileDownloader.EXTRA_FILE, file);
+ i.putExtra(FileDownloader.EXTRA_FILE, (Parcelable)file);
startService(i);
}
}
-
+
private void sendDownloadedFile(){
getFileOperationsHelper().sendDownloadedFile(mWaitingToSend);
mWaitingToSend = null;
}
-
+
/**
* Requests the download of the received {@link OCFile} , updates the UI
* to monitor the download progress and prepares the activity to send the file
* when the download finishes.
- *
+ *
* @param file {@link OCFile} to download and preview.
*/
public void startDownloadForSending(OCFile file) {
@@ -1608,23 +1633,23 @@ public void startDownloadForSending(OCFile file) {
boolean hasSecondFragment = (getSecondFragment()!= null);
updateFragmentsVisibility(hasSecondFragment);
}
-
+
/**
* Opens the image gallery showing the image {@link OCFile} received as parameter.
- *
+ *
* @param file Image {@link OCFile} to show.
*/
public void startImagePreview(OCFile file) {
Intent showDetailsIntent = new Intent(this, PreviewImageActivity.class);
- showDetailsIntent.putExtra(EXTRA_FILE, file);
+ showDetailsIntent.putExtra(EXTRA_FILE, (Parcelable)file);
showDetailsIntent.putExtra(EXTRA_ACCOUNT, getAccount());
startActivity(showDetailsIntent);
-
+
}
/**
* Stars the preview of an already down media {@link OCFile}.
- *
+ *
* @param file Media {@link OCFile} to preview.
* @param startPlaybackPosition Media position where the playback will be started,
* in milliseconds.
@@ -1644,7 +1669,7 @@ public void startMediaPreview(OCFile file, int startPlaybackPosition, boolean au
* Requests the download of the received {@link OCFile} , updates the UI
* to monitor the download progress and prepares the activity to preview
* or open the file when the download finishes.
- *
+ *
* @param file {@link OCFile} to download and preview.
*/
public void startDownloadForPreview(OCFile file) {
@@ -1660,7 +1685,7 @@ public void startDownloadForPreview(OCFile file) {
public void cancelTransference(OCFile file) {
getFileOperationsHelper().cancelTransference(file);
- if (mWaitingToPreview != null &&
+ if (mWaitingToPreview != null &&
mWaitingToPreview.getRemotePath().equals(file.getRemotePath())) {
mWaitingToPreview = null;
}
diff --git a/src/com/owncloud/android/ui/activity/FolderPickerActivity.java b/src/com/owncloud/android/ui/activity/FolderPickerActivity.java
index 4b558f00bdd..062b2db92fe 100644
--- a/src/com/owncloud/android/ui/activity/FolderPickerActivity.java
+++ b/src/com/owncloud/android/ui/activity/FolderPickerActivity.java
@@ -375,11 +375,12 @@ public void onClick(View v) {
Parcelable targetFile = i.getParcelableExtra(FolderPickerActivity.EXTRA_FILE);
Intent data = new Intent();
- data.putExtra(EXTRA_FOLDER, getCurrentFolder());
+ data.putExtra(EXTRA_FOLDER, (Parcelable)getCurrentFolder());
if (targetFile != null) {
data.putExtra(EXTRA_FILE, targetFile);
}
setResult(RESULT_OK, data);
+
finish();
}
}
diff --git a/src/com/owncloud/android/ui/activity/Preferences.java b/src/com/owncloud/android/ui/activity/Preferences.java
index e1c5c10deec..25d9849eb84 100644
--- a/src/com/owncloud/android/ui/activity/Preferences.java
+++ b/src/com/owncloud/android/ui/activity/Preferences.java
@@ -25,6 +25,7 @@
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -44,12 +45,14 @@
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceManager;
+
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
+
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
@@ -70,10 +73,10 @@
import com.owncloud.android.authentication.AuthenticatorActivity;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.db.DbHandler;
+import com.owncloud.android.db.UploadDbHandler;
import com.owncloud.android.files.FileOperationsHelper;
import com.owncloud.android.files.services.FileDownloader;
-import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.files.services.FileUploadService;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.services.OperationsService;
import com.owncloud.android.ui.RadioButtonPreference;
@@ -91,10 +94,12 @@ public class Preferences extends PreferenceActivity
private static final String TAG = "OwnCloudPreferences";
+
+ private UploadDbHandler mDbHandler;
+
private static final int ACTION_SELECT_UPLOAD_PATH = 1;
private static final int ACTION_SELECT_UPLOAD_VIDEO_PATH = 2;
- private DbHandler mDbHandler;
private CheckBoxPreference pCode;
private Preference pAboutApp;
private AppCompatDelegate mDelegate;
@@ -114,7 +119,7 @@ public class Preferences extends PreferenceActivity
private String mUploadVideoPath;
protected FileDownloader.FileDownloaderBinder mDownloaderBinder = null;
- protected FileUploader.FileUploaderBinder mUploaderBinder = null;
+ protected FileUploadService.FileUploaderBinder mUploaderBinder = null;
private ServiceConnection mDownloadServiceConnection, mUploadServiceConnection = null;
@SuppressWarnings("deprecation")
@@ -123,7 +128,7 @@ public void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
- mDbHandler = new DbHandler(getBaseContext());
+ mDbHandler = UploadDbHandler.getInstance(getBaseContext());
addPreferencesFromResource(R.xml.preferences);
ActionBar actionBar = getSupportActionBar();
@@ -296,7 +301,8 @@ public boolean onPreferenceClick(Preference preference) {
String username = currentAccount.name.substring(0,
currentAccount.name.lastIndexOf('@'));
- String recommendSubject = String.format(getString(R.string.recommend_subject),
+ String recommendSubject =
+ String.format(getString(R.string.recommend_subject),
appName);
String recommendText = String.format(getString(R.string.recommend_text),
appName, downloadUrl);
@@ -323,7 +329,8 @@ public boolean onPreferenceClick(Preference preference) {
@Override
public boolean onPreferenceClick(Preference preference) {
String feedbackMail =(String) getText(R.string.mail_feedback);
- String feedback =(String) getText(R.string.prefs_feedback) + " - android v" + appVersion;
+ String feedback =(String) getText(R.string.prefs_feedback) +
+ " - android v" + appVersion;
Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, feedback);
@@ -431,7 +438,8 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
/* About App */
pAboutApp = (Preference) findPreference("about_app");
if (pAboutApp != null) {
- pAboutApp.setTitle(String.format(getString(R.string.about_android), getString(R.string.app_name)));
+ pAboutApp.setTitle(String.format(getString(R.string.about_android),
+ getString(R.string.app_name)));
pAboutApp.setSummary(String.format(getString(R.string.about_version), appVersion));
}
@@ -446,7 +454,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
}
mUploadServiceConnection = newTransferenceServiceConnection();
if (mUploadServiceConnection != null) {
- bindService(new Intent(this, FileUploader.class), mUploadServiceConnection,
+ bindService(new Intent(this, FileUploadService.class), mUploadServiceConnection,
Context.BIND_AUTO_CREATE);
}
@@ -827,7 +835,7 @@ public FileDownloader.FileDownloaderBinder getFileDownloaderBinder() {
@Override
- public FileUploader.FileUploaderBinder getFileUploaderBinder() {
+ public FileUploadService.FileUploaderBinder getFileUploaderBinder() {
return mUploaderBinder;
}
@@ -859,9 +867,10 @@ public void onServiceConnected(ComponentName component, IBinder service) {
if (component.equals(new ComponentName(Preferences.this, FileDownloader.class))) {
mDownloaderBinder = (FileDownloader.FileDownloaderBinder) service;
- } else if (component.equals(new ComponentName(Preferences.this, FileUploader.class))) {
+ } else if (component.equals(new ComponentName(Preferences.this,
+ FileUploadService.class))) {
Log_OC.d(TAG, "Upload service connected");
- mUploaderBinder = (FileUploader.FileUploaderBinder) service;
+ mUploaderBinder = (FileUploadService.FileUploaderBinder) service;
} else {
return;
}
@@ -873,7 +882,8 @@ public void onServiceDisconnected(ComponentName component) {
if (component.equals(new ComponentName(Preferences.this, FileDownloader.class))) {
Log_OC.d(TAG, "Download service suddenly disconnected");
mDownloaderBinder = null;
- } else if (component.equals(new ComponentName(Preferences.this, FileUploader.class))) {
+ } else if (component.equals(new ComponentName(Preferences.this,
+ FileUploadService.class))) {
Log_OC.d(TAG, "Upload service suddenly disconnected");
mUploaderBinder = null;
}
diff --git a/src/com/owncloud/android/ui/activity/UploadFilesActivity.java b/src/com/owncloud/android/ui/activity/UploadFilesActivity.java
index 9a3ce17846e..0e8ae350faa 100644
--- a/src/com/owncloud/android/ui/activity/UploadFilesActivity.java
+++ b/src/com/owncloud/android/ui/activity/UploadFilesActivity.java
@@ -328,8 +328,8 @@ protected Boolean doInBackground(Void... params) {
/**
* Updates the activity UI after the check of space is done.
*
- * If there is not space enough. shows a new dialog to query the user if wants to move the files instead
- * of copy them.
+ * If there is not space enough. shows a new dialog to query the user if wants to move the
+ * files instead of copy them.
*
* @param result 'True' when there is space enough to copy all the selected files.
*/
@@ -350,7 +350,8 @@ protected void onPostExecute(Boolean result) {
// to the ownCloud folder instead of copying
String[] args = {getString(R.string.app_name)};
ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(
- R.string.upload_query_move_foreign_files, args, R.string.common_yes, -1, R.string.common_no
+ R.string.upload_query_move_foreign_files, args, R.string.common_yes, -1,
+ R.string.common_no
);
dialog.setOnConfirmationListener(UploadFilesActivity.this);
dialog.show(getSupportFragmentManager(), QUERY_TO_MOVE_DIALOG_TAG);
diff --git a/src/com/owncloud/android/ui/activity/UploadListActivity.java b/src/com/owncloud/android/ui/activity/UploadListActivity.java
new file mode 100755
index 00000000000..725b11f0b1f
--- /dev/null
+++ b/src/com/owncloud/android/ui/activity/UploadListActivity.java
@@ -0,0 +1,202 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author LukeOwncloud
+ * 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 .
+ *
+ */
+package com.owncloud.android.ui.activity;
+
+import java.io.File;
+
+import android.accounts.Account;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcelable;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+
+import com.owncloud.android.R;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.db.UploadDbHandler;
+import com.owncloud.android.db.UploadDbObject;
+import com.owncloud.android.files.services.FileUploadService;
+import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.ui.errorhandling.ExceptionHandler;
+import com.owncloud.android.ui.fragment.UploadListFragment;
+import com.owncloud.android.ui.preview.PreviewImageActivity;
+
+/**
+ * Activity listing pending, active, and completed uploads. User can delete
+ * completed uploads from view. Content of this list of coming from
+ * {@link UploadDbHandler}.
+ *
+ */
+public class UploadListActivity extends FileActivity implements UploadListFragment.ContainerActivity {
+
+ private static final String TAG = UploadListActivity.class.getSimpleName();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this));
+ setContentView(R.layout.upload_list_layout);
+ }
+
+ // ////////////////////////////////////////
+ // UploadListFragment.ContainerActivity
+ // ////////////////////////////////////////
+ @Override
+ public boolean onUploadItemClick(UploadDbObject file) {
+ File f = new File(file.getLocalPath());
+ if(!f.exists()) {
+ Toast.makeText(this, "Cannot open. Local file does not exist.",
+ Toast.LENGTH_SHORT).show();
+ } else {
+ openFileWithDefault(file.getLocalPath());
+ }
+ return true;
+ }
+
+ /**
+ * Open file with app associates with its mimetype. If mimetype unknown, show list with all apps.
+ */
+ private void openFileWithDefault(String localPath) {
+ Intent myIntent = new Intent(android.content.Intent.ACTION_VIEW);
+ File file = new File(localPath);
+ String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString());
+ String mimetype = android.webkit.MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ if (mimetype == null)
+ mimetype = "*/*";
+ myIntent.setDataAndType(Uri.fromFile(file), mimetype);
+ try {
+ startActivity(myIntent);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this, "Found no app to open this file.", Toast.LENGTH_LONG).show();
+ Log_OC.i(TAG, "Could not find app for sending log history.");
+
+ }
+ }
+
+ /**
+ * Same as openFileWithDefault() but user cannot save default app.
+ * @param ocFile
+ */
+ @SuppressWarnings("unused")
+ private void openFileWithDefaultNoDefault(OCFile ocFile) {
+ getFileOperationsHelper().openFile(ocFile);
+ }
+
+ /**
+ * WARNING! This opens the local copy inside owncloud directory. If file not uploaded yet,
+ * there is none.
+ */
+ @SuppressWarnings("unused")
+ private void openPreview(UploadDbObject file) {
+ // preview image
+ Intent showDetailsIntent = new Intent(this, PreviewImageActivity.class);
+ showDetailsIntent.putExtra(EXTRA_FILE, (Parcelable)file.getOCFile());
+ showDetailsIntent.putExtra(EXTRA_ACCOUNT, getAccount());
+ startActivity(showDetailsIntent);
+ }
+
+ @SuppressWarnings("unused")
+ private void openDetails(UploadDbObject file) {
+ OCFile ocFile = file.getOCFile();
+ Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class);
+ showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable) ocFile);
+ showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, file.getAccount(this));
+ startActivity(showDetailsIntent);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ boolean retval = true;
+ switch (item.getItemId()) {
+ case R.id.action_retry_uploads: {
+ Log_OC.d(TAG, "FileUploadService.retry() called by onMenuItemSelected()");
+ FileUploadService.retry(this);
+ break;
+ }
+ case R.id.action_clear_failed_uploads: {
+ UploadDbHandler db = UploadDbHandler.getInstance(this);
+ db.clearFailedUploads();
+ break;
+ }
+ case R.id.action_clear_finished_uploads: {
+ UploadDbHandler db = UploadDbHandler.getInstance(this);
+ db.clearFinishedUploads();
+ break;
+ }
+ default:
+ retval = super.onOptionsItemSelected(item);
+ }
+ return retval;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.upload_list_menu, menu);
+ return true;
+ }
+
+ @Override
+ protected ServiceConnection newTransferenceServiceConnection() {
+ return new UploadListServiceConnection();
+ }
+
+ /** Defines callbacks for service binding, passed to bindService() */
+ private class UploadListServiceConnection implements ServiceConnection {
+
+ @Override
+ public void onServiceConnected(ComponentName component, IBinder service) {
+ if (service instanceof FileUploaderBinder) {
+ if(mUploaderBinder == null)
+ {
+ mUploaderBinder = (FileUploaderBinder) service;
+ Log_OC.d(TAG, "UploadListActivity connected to Upload service. component: " +
+ component + " service: "
+ + service);
+ } else {
+ Log_OC.d(TAG, "mUploaderBinder already set. mUploaderBinder: " +
+ mUploaderBinder + " service:" + service);
+ }
+ } else {
+ Log_OC.d(TAG, "UploadListActivity not connected to Upload service. component: " +
+ component + " service: " + service);
+ return;
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName component) {
+ if (component.equals(new ComponentName(UploadListActivity.this, FileUploadService.class))) {
+ Log_OC.d(TAG, "UploadListActivity suddenly disconnected from Upload service");
+ mUploaderBinder = null;
+ }
+ }
+ };
+
+}
diff --git a/src/com/owncloud/android/ui/activity/Uploader.java b/src/com/owncloud/android/ui/activity/Uploader.java
index 7bea4bb39fc..07a1b4f7ceb 100644
--- a/src/com/owncloud/android/ui/activity/Uploader.java
+++ b/src/com/owncloud/android/ui/activity/Uploader.java
@@ -22,19 +22,14 @@
package com.owncloud.android.ui.activity;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Stack;
-import java.util.Vector;
-
-
import android.accounts.Account;
import android.accounts.AccountManager;
+
+import android.annotation.SuppressLint;
+
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AlertDialog.Builder;
+
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
@@ -72,10 +67,10 @@
import com.owncloud.android.R;
import com.owncloud.android.authentication.AccountAuthenticator;
import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.files.services.FileUploadService;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.CreateFolderOperation;
import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
import com.owncloud.android.ui.dialog.LoadingDialog;
@@ -83,6 +78,14 @@
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.ErrorMessageAdapter;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Stack;
+import java.util.Vector;
+
/**
* This can be used to upload things to an ownCloud instance.
@@ -220,7 +223,8 @@ public void onShow(DialogInterface dialog) {
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setTitle(R.string.uploader_wrn_no_account_title);
builder.setMessage(String.format(
- getString(R.string.uploader_wrn_no_account_text), getString(R.string.app_name)));
+ getString(R.string.uploader_wrn_no_account_text),
+ getString(R.string.app_name)));
builder.setCancelable(false);
builder.setPositiveButton(R.string.uploader_wrn_no_account_setup_btn_text, new OnClickListener() {
@Override
@@ -465,7 +469,8 @@ private boolean somethingToUpload() {
return (mStreamsToUpload != null && mStreamsToUpload.get(0) != null);
}
- public void uploadFiles() {
+ @SuppressLint("NewApi")
+ public void uploadFiles() {
try {
// ArrayList for files with path in external storage
@@ -561,12 +566,14 @@ public void uploadFiles() {
throw new SecurityException();
}
- Intent intent = new Intent(getApplicationContext(), FileUploader.class);
- intent.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES);
- intent.putExtra(FileUploader.KEY_LOCAL_FILE, local.toArray(new String[local.size()]));
- intent.putExtra(FileUploader.KEY_REMOTE_FILE,
+ Intent intent = new Intent(getApplicationContext(), FileUploadService.class);
+ intent.putExtra(FileUploadService.KEY_UPLOAD_TYPE,
+ FileUploadService.UploadSingleMulti.UPLOAD_MULTIPLE_FILES);
+ intent.putExtra(FileUploadService.KEY_LOCAL_FILE,
+ local.toArray(new String[local.size()]));
+ intent.putExtra(FileUploadService.KEY_REMOTE_FILE,
remote.toArray(new String[remote.size()]));
- intent.putExtra(FileUploader.KEY_ACCOUNT, getAccount());
+ intent.putExtra(FileUploadService.KEY_ACCOUNT, getAccount());
startService(intent);
//Save the path to shared preferences
@@ -682,11 +689,12 @@ public void onTmpFileCopied(String result, int index) {
dismissWaitingCopyDialog();
}
if (result != null) {
- Intent intent = new Intent(getApplicationContext(), FileUploader.class);
- intent.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);
- intent.putExtra(FileUploader.KEY_LOCAL_FILE, result);
- intent.putExtra(FileUploader.KEY_REMOTE_FILE, mRemoteCacheData.get(index));
- intent.putExtra(FileUploader.KEY_ACCOUNT, getAccount());
+ Intent intent = new Intent(getApplicationContext(), FileUploadService.class);
+ intent.putExtra(FileUploadService.KEY_UPLOAD_TYPE,
+ FileUploadService.UploadSingleMulti.UPLOAD_SINGLE_FILE);
+ intent.putExtra(FileUploadService.KEY_LOCAL_FILE, result);
+ intent.putExtra(FileUploadService.KEY_REMOTE_FILE, mRemoteCacheData.get(index));
+ intent.putExtra(FileUploadService.KEY_ACCOUNT, getAccount());
startService(intent);
} else {
diff --git a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java
new file mode 100755
index 00000000000..b892f494614
--- /dev/null
+++ b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java
@@ -0,0 +1,425 @@
+package com.owncloud.android.ui.adapter;
+
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Observable;
+import java.util.Observer;
+
+import android.app.Activity;
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.graphics.Bitmap;
+import android.text.format.DateUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.Button;
+import android.widget.ExpandableListView;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.owncloud.android.R;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.ThumbnailsCacheManager;
+import com.owncloud.android.db.UploadDbHandler;
+import com.owncloud.android.db.UploadDbHandler.UploadStatus;
+import com.owncloud.android.db.UploadDbObject;
+import com.owncloud.android.files.services.FileUploadService;
+import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.operations.UploadFileOperation;
+import com.owncloud.android.ui.activity.FileActivity;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.UploadUtils;
+
+/**
+ * This Adapter populates a ListView with following types of uploads: pending,
+ * active, completed. Filtering possible.
+ *
+ */
+public class ExpandableUploadListAdapter extends BaseExpandableListAdapter implements Observer {
+
+ private static final String TAG = "ExpandableUploadListAdapter";
+ private Activity mActivity;
+
+ public ProgressListener mProgressListener;
+ UploadFileOperation mCurrentUpload;
+
+ interface Refresh {
+ public void refresh();
+ }
+ abstract class UploadGroup implements Refresh {
+ UploadDbObject[] items;
+ String name;
+ public UploadGroup(String groupName) {
+ this.name = groupName;
+ items = new UploadDbObject[0];
+ }
+ public String getGroupName() {
+ return name;
+ }
+ public Comparator comparator = new Comparator() {
+ @Override
+ public int compare(UploadDbObject lhs, UploadDbObject rhs) {
+ return compareUploadTime(lhs, rhs);
+ }
+ private int compareUploadTime(UploadDbObject lhs, UploadDbObject rhs) {
+ return rhs.getUploadTime().compareTo(lhs.getUploadTime());
+ }
+ };
+ abstract public int getGroupIcon();
+ }
+ private UploadGroup[] mUploadGroups = null;
+ UploadDbHandler mDb;
+
+ FileActivity parentFileActivity;
+ public void setFileActivity(FileActivity parentFileActivity) {
+ this.parentFileActivity = parentFileActivity;
+ }
+
+ public ExpandableUploadListAdapter(Activity context) {
+ Log_OC.d(TAG, "UploadListAdapter");
+ mActivity = context;
+ mDb = UploadDbHandler.getInstance(mActivity);
+ mUploadGroups = new UploadGroup[3];
+ mUploadGroups[0] = new UploadGroup("Current Uploads") {
+ @Override
+ public void refresh() {
+ items = mDb.getCurrentAndPendingUploads();
+ Arrays.sort(items, comparator);
+ }
+ @Override
+ public int getGroupIcon() {
+ return R.drawable.upload_in_progress;
+ }
+ };
+ mUploadGroups[1] = new UploadGroup("Failed Uploads"){
+ @Override
+ public void refresh() {
+ items = mDb.getFailedUploads();
+ Arrays.sort(items, comparator);
+ }
+ @Override
+ public int getGroupIcon() {
+ return R.drawable.upload_failed;
+ }
+
+ };
+ mUploadGroups[2] = new UploadGroup("Finished Uploads"){
+ @Override
+ public void refresh() {
+ items = mDb.getFinishedUploads();
+ Arrays.sort(items, comparator);
+ }
+ @Override
+ public int getGroupIcon() {
+ return R.drawable.upload_finished;
+ }
+
+ };
+ loadUploadItemsFromDb();
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver observer) {
+ super.registerDataSetObserver(observer);
+ mDb.addObserver(this);
+ Log_OC.d(TAG, "registerDataSetObserver");
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ super.unregisterDataSetObserver(observer);
+ mDb.deleteObserver(this);
+ Log_OC.d(TAG, "unregisterDataSetObserver");
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return true;
+ }
+
+ private View getView(UploadDbObject[] uploadsItems, int position, View convertView, ViewGroup parent) {
+ View view = convertView;
+ if (view == null) {
+ LayoutInflater inflator = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ view = inflator.inflate(R.layout.upload_list_item, null);
+ }
+ if (uploadsItems != null && uploadsItems.length > position) {
+ final UploadDbObject uploadObject = uploadsItems[position];
+
+ TextView fileName = (TextView) view.findViewById(R.id.upload_name);
+ String file = uploadObject.getOCFile().getFileName();
+ fileName.setText(file);
+
+ TextView localPath = (TextView) view.findViewById(R.id.upload_local_path);
+ String path = uploadObject.getOCFile().getStoragePath();
+ path = path.substring(0, path.length() - file.length() - 1);
+ localPath.setText("Path: " + path);
+
+ TextView fileSize = (TextView) view.findViewById(R.id.upload_file_size);
+ fileSize.setText(DisplayUtils.bytesToHumanReadable(uploadObject.getOCFile().getFileLength()));
+
+ TextView statusView = (TextView) view.findViewById(R.id.upload_status);
+ String status;
+ switch (uploadObject.getUploadStatus()) {
+ case UPLOAD_IN_PROGRESS:
+ status = mActivity.getResources().getString(R.string.uploader_upload_in_progress_ticker);
+ ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.upload_progress_bar);
+ progressBar.setProgress(0);
+ progressBar.setVisibility(View.VISIBLE);
+ mProgressListener = new ProgressListener(progressBar);
+ if(parentFileActivity.getFileUploaderBinder() != null) {
+ mCurrentUpload = parentFileActivity.getFileUploaderBinder().getCurrentUploadOperation();
+ if(mCurrentUpload != null) {
+ mCurrentUpload.addDatatransferProgressListener(mProgressListener);
+ Log_OC.d(TAG, "added progress listener for current upload: " + mCurrentUpload);
+ } else {
+ Log_OC.w(TAG, "getFileUploaderBinder().getCurrentUploadOperation() return null. That is odd.");
+ }
+ } else {
+ Log_OC.e(TAG, "UploadBinder == null. It should have been created on creating parentFileActivity"
+ + " which inherits from FileActivity. Fix that!");
+ }
+ break;
+ case UPLOAD_FAILED_GIVE_UP:
+ if (uploadObject.getLastResult() != null) {
+ status = "Upload failed: " + uploadObject.getLastResult().getLogMessage();
+ } else {
+ status = "Upload failed.";
+ }
+ break;
+ case UPLOAD_FAILED_RETRY:
+ if(uploadObject.getLastResult() != null){
+ status = "Last failure: "
+ + uploadObject.getLastResult().getLogMessage();
+ } else {
+ status = "Upload will be retried shortly.";
+ }
+ String laterReason = FileUploadService.getUploadLaterReason(mActivity, uploadObject);
+ if(laterReason != null) {
+ //Upload failed once but is delayed now, show reason.
+ status += "\n" + laterReason;
+ }
+ break;
+ case UPLOAD_LATER:
+ status = FileUploadService.getUploadLaterReason(mActivity, uploadObject);
+ break;
+ case UPLOAD_SUCCEEDED:
+ status = "Completed.";
+ break;
+ case UPLOAD_CANCELLED:
+ status = "Upload cancelled.";
+ break;
+ case UPLOAD_PAUSED:
+ status = "Upload paused.";
+ break;
+ default:
+ status = uploadObject.getUploadStatus().toString();
+ if(uploadObject.getLastResult() != null){
+ uploadObject.getLastResult().getLogMessage();
+ }
+ break;
+ }
+ if(uploadObject.getUploadStatus() != UploadStatus.UPLOAD_IN_PROGRESS) {
+ ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.upload_progress_bar);
+ progressBar.setVisibility(View.GONE);
+ if (parentFileActivity.getFileUploaderBinder() != null && mProgressListener != null
+ && mCurrentUpload != null) {
+ OCFile currentOcFile = mCurrentUpload.getFile();
+ parentFileActivity.getFileUploaderBinder().removeDatatransferProgressListener(mProgressListener,
+ uploadObject.getAccount(mActivity), currentOcFile);
+ mProgressListener = null;
+ mCurrentUpload = null;
+ }
+ }
+ statusView.setText(status);
+
+ Button rightButton = (Button) view.findViewById(R.id.upload_right_button);
+ if (UploadUtils.userCanRetryUpload(uploadObject)
+ && uploadObject.getUploadStatus() != UploadStatus.UPLOAD_SUCCEEDED) {
+ rightButton.setText("\u21BA"); //Anticlockwise Open Circle Arrow U+21BA
+ rightButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ parentFileActivity.getFileOperationsHelper().retryUpload(uploadObject);
+ }
+ });
+ } else if (UploadUtils.userCanCancelUpload(uploadObject)) {
+ rightButton.setText("\u274C"); //Cross Mark U+274C
+ rightButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ parentFileActivity.getFileOperationsHelper().cancelTransference(uploadObject.getOCFile());
+ }
+ });
+ } else {
+ rightButton.setText("\u267B"); //Black Universal Recycling Symbol U+267B
+ rightButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ parentFileActivity.getFileOperationsHelper().removeUploadFromList(uploadObject);
+ }
+ });
+ }
+
+
+ ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1);
+ fileIcon.setImageResource(R.drawable.file);
+ try {
+ //TODO Wait for https://github.com/owncloud/android/pull/746 and add thumbnail.
+ Bitmap b = ThumbnailsCacheManager.getBitmapFromDiskCache(uploadObject.getOCFile().getRemoteId());
+ if (b != null) {
+ fileIcon.setImageBitmap(b);
+ }
+ } catch (NullPointerException e) {
+ }
+
+ TextView uploadDate = (TextView) view.findViewById(R.id.upload_date);
+ CharSequence dateString = DisplayUtils.getRelativeDateTimeString(mActivity, uploadObject.getUploadTime()
+ .getTimeInMillis(), DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0);
+ uploadDate.setText(dateString);
+ }
+
+ return view;
+ }
+
+
+
+ @Override
+ public boolean hasStableIds() {
+ return false;
+ }
+
+
+
+ /**
+ * Load upload items from {@link UploadDbHandler}.
+ */
+ private void loadUploadItemsFromDb() {
+ Log_OC.d(TAG, "loadUploadItemsFromDb");
+
+ for (UploadGroup group : mUploadGroups) {
+ group.refresh();
+ }
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ notifyDataSetChanged();
+ }
+ });
+
+ }
+
+ @Override
+ public void update(Observable arg0, Object arg1) {
+ Log_OC.d(TAG, "update");
+ loadUploadItemsFromDb();
+ }
+
+ @Override
+ public Object getChild(int groupPosition, int childPosition) {
+ return mUploadGroups[(int) getGroupId(groupPosition)].items[childPosition];
+ }
+
+ @Override
+ public long getChildId(int groupPosition, int childPosition) {
+ return childPosition;
+ }
+
+ @Override
+ public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView,
+ ViewGroup parent) {
+ return getView(mUploadGroups[(int) getGroupId(groupPosition)].items, childPosition, convertView, parent);
+ }
+
+ @Override
+ public int getChildrenCount(int groupPosition) {
+ return mUploadGroups[(int) getGroupId(groupPosition)].items.length;
+ }
+
+ @Override
+ public Object getGroup(int groupPosition) {
+ return mUploadGroups[(int) getGroupId(groupPosition)];
+ }
+
+ @Override
+ public int getGroupCount() {
+ int size = 0;
+ for (UploadGroup uploadGroup : mUploadGroups) {
+ if(uploadGroup.items.length > 0) {
+ size++;
+ }
+ }
+ return size;
+ }
+
+ /**
+ * Returns the groupId (that is, index in mUploadGroups) for group at position groupPosition (0-based).
+ * Could probably be done more intuitive but this tested methods works as intended.
+ */
+ @Override
+ public long getGroupId(int groupPosition) {
+ int id = -1;
+ for (int i = 0; i <= groupPosition; ) {
+ id++;
+ if(mUploadGroups[id].items.length > 0){
+ i++;
+ }
+ }
+ return id;
+ }
+
+ @Override
+ public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
+ //force group to stay unfolded
+ ExpandableListView listView = (ExpandableListView) parent;
+ listView.expandGroup(groupPosition);
+
+ listView.setGroupIndicator(null);
+
+ UploadGroup group = (UploadGroup) getGroup(groupPosition);
+ if (convertView == null) {
+ LayoutInflater infalInflater = (LayoutInflater) mActivity
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ convertView = infalInflater.inflate(R.layout.upload_list_group, null);
+ }
+ TextView tv = (TextView) convertView.findViewById(R.id.uploadListGroupName);
+ tv.setText(group.getGroupName());
+// ImageView icon = (ImageView) convertView.findViewById(R.id.uploadListGroupIcon);
+// icon.setImageResource(group.getGroupIcon());
+ return convertView;
+ }
+
+ @Override
+ public boolean isChildSelectable(int groupPosition, int childPosition) {
+ return true;
+ }
+
+ private class ProgressListener implements OnDatatransferProgressListener {
+ int mLastPercent = 0;
+ WeakReference mProgressBar = null;
+
+ ProgressListener(ProgressBar progressBar) {
+ mProgressBar = new WeakReference(progressBar);
+ }
+
+ @Override
+ public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filename) {
+ int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer));
+ if (percent != mLastPercent) {
+ ProgressBar pb = mProgressBar.get();
+ if (pb != null) {
+ pb.setProgress(percent);
+ pb.postInvalidate();
+ }
+ }
+ mLastPercent = percent;
+ }
+
+ };
+}
diff --git a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java
index 33fa57fd916..50a98d2da64 100644
--- a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java
+++ b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java
@@ -49,7 +49,7 @@
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder;
import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
import com.owncloud.android.ui.activity.ComponentsGetter;
import com.owncloud.android.utils.DisplayUtils;
@@ -87,12 +87,14 @@ public FileListListAdapter(
mJustFolders = justFolders;
mContext = context;
mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);
+
mTransferServiceGetter = transferServiceGetter;
mAppPreferences = PreferenceManager
.getDefaultSharedPreferences(mContext);
// Read sorting order, default to sort by name ascending
+
FileStorageUtils.mSortOrder = mAppPreferences.getInt("sortOrder", 0);
FileStorageUtils.mSortAscending = mAppPreferences.getBoolean("sortAscending", true);
@@ -325,6 +327,7 @@ public View getView(int position, View convertView, ViewGroup parent) {
file.getFileName()));
}
+
} else {
// Folder
if (checkIfFileIsSharedWithMe(file)) {
diff --git a/src/com/owncloud/android/ui/errorhandling/ErrorShowActivity.java b/src/com/owncloud/android/ui/errorhandling/ErrorShowActivity.java
new file mode 100755
index 00000000000..cea31c5acd2
--- /dev/null
+++ b/src/com/owncloud/android/ui/errorhandling/ErrorShowActivity.java
@@ -0,0 +1,26 @@
+package com.owncloud.android.ui.errorhandling;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+
+import com.owncloud.android.R;
+
+public class ErrorShowActivity extends Activity {
+
+ private static final String TAG = ErrorShowActivity.class.getName();
+
+ TextView error;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.e(TAG, "ErrorShowActivity was called. See above for StackTrace.");
+ Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this));
+ setContentView(R.layout.errorhandling_showerror);
+ error = (TextView) findViewById(R.id.errorTextView);
+ error.setText(getIntent().getStringExtra("error"));
+
+ }
+}
\ No newline at end of file
diff --git a/src/com/owncloud/android/ui/errorhandling/ExceptionHandler.java b/src/com/owncloud/android/ui/errorhandling/ExceptionHandler.java
new file mode 100755
index 00000000000..d9f19f38e4c
--- /dev/null
+++ b/src/com/owncloud/android/ui/errorhandling/ExceptionHandler.java
@@ -0,0 +1,66 @@
+package com.owncloud.android.ui.errorhandling;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Build;
+import android.util.Log;
+
+public class ExceptionHandler implements java.lang.Thread.UncaughtExceptionHandler {
+ private final Activity myContext;
+ private final String LINE_SEPARATOR = "\n";
+
+ private static final String TAG = ExceptionHandler.class.getName();
+
+ public ExceptionHandler(Activity context) {
+ myContext = context;
+ }
+
+ public void uncaughtException(Thread thread, Throwable exception) {
+ Log.e(TAG, "ExceptionHandler caught UncaughtException", exception);
+ StringWriter stackTrace = new StringWriter();
+ exception.printStackTrace(new PrintWriter(stackTrace));
+ StringBuilder errorReport = new StringBuilder();
+ errorReport.append("************ CAUSE OF ERROR ************\n\n");
+ errorReport.append(stackTrace.toString());
+
+ errorReport.append("\n************ DEVICE INFORMATION ***********\n");
+ errorReport.append("Brand: ");
+ errorReport.append(Build.BRAND);
+ errorReport.append(LINE_SEPARATOR);
+ errorReport.append("Device: ");
+ errorReport.append(Build.DEVICE);
+ errorReport.append(LINE_SEPARATOR);
+ errorReport.append("Model: ");
+ errorReport.append(Build.MODEL);
+ errorReport.append(LINE_SEPARATOR);
+ errorReport.append("Id: ");
+ errorReport.append(Build.ID);
+ errorReport.append(LINE_SEPARATOR);
+ errorReport.append("Product: ");
+ errorReport.append(Build.PRODUCT);
+ errorReport.append(LINE_SEPARATOR);
+ errorReport.append("\n************ FIRMWARE ************\n");
+ errorReport.append("SDK: ");
+ errorReport.append(Build.VERSION.SDK_INT);
+ errorReport.append(LINE_SEPARATOR);
+ errorReport.append("Release: ");
+ errorReport.append(Build.VERSION.RELEASE);
+ errorReport.append(LINE_SEPARATOR);
+ errorReport.append("Incremental: ");
+ errorReport.append(Build.VERSION.INCREMENTAL);
+ errorReport.append(LINE_SEPARATOR);
+
+ Log.e(TAG, "An exception was thrown and handled by ExceptionHandler:", exception);
+
+ Intent intent = new Intent(myContext, ErrorShowActivity.class);
+ intent.putExtra("error", errorReport.toString());
+ myContext.startActivity(intent);
+
+ android.os.Process.killProcess(android.os.Process.myPid());
+ System.exit(1000);
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/owncloud/android/ui/fragment/ExpandableListFragment.java b/src/com/owncloud/android/ui/fragment/ExpandableListFragment.java
new file mode 100755
index 00000000000..6d064aee75d
--- /dev/null
+++ b/src/com/owncloud/android/ui/fragment/ExpandableListFragment.java
@@ -0,0 +1,91 @@
+/* ownCloud Android client application
+ * Copyright (C) 2012 Bartek Przybylski
+ * Copyright (C) 2012-2013 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 .
+ *
+ */
+
+package com.owncloud.android.ui.fragment;
+
+import android.os.Bundle;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemLongClickListener;
+import android.widget.ExpandableListAdapter;
+import android.widget.ExpandableListView;
+import android.widget.ExpandableListView.OnChildClickListener;
+import android.widget.TextView;
+
+import com.owncloud.android.R;
+import com.owncloud.android.lib.common.utils.Log_OC;
+
+/**
+ * Extending ExtendedListFragment. This allows dividing list in groups.
+ */
+public class ExpandableListFragment extends ExtendedListFragment implements OnChildClickListener
+ {
+ protected static final String TAG = ExpandableListFragment.class.getSimpleName();
+
+ protected ExpandableListView mList;
+
+ public void setListAdapter(ExpandableListAdapter listAdapter) {
+ mList.setAdapter(listAdapter);
+ mList.invalidate();
+ }
+
+ public ExpandableListView getListView() {
+ return mList;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ Log_OC.e(TAG, "onCreateView");
+
+ View v = inflater.inflate(R.layout.list_fragment_expandable, null);
+ mEmptyListMessage = (TextView) v.findViewById(R.id.empty_list_view);
+ mList = (ExpandableListView)(v.findViewById(R.id.list_root));
+ mList.setOnChildClickListener(this);
+
+ mList.setDivider(getResources().getDrawable(R.drawable.uploader_list_separator));
+ mList.setDividerHeight(1);
+
+// if (savedInstanceState != null) {
+// int referencePosition = savedInstanceState.getInt(KEY_SAVED_LIST_POSITION);
+// setReferencePosition(referencePosition);
+// }
+
+ // Pull down refresh
+ mRefreshListLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_refresh_files);
+ mRefreshEmptyLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_refresh_files_emptyView);
+
+ onCreateSwipeToRefresh(mRefreshListLayout);
+ onCreateSwipeToRefresh(mRefreshEmptyLayout);
+
+ mList.setEmptyView(mRefreshEmptyLayout);
+
+ return v;
+ }
+
+ @Override
+ public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
+ // to be @overriden
+ Log_OC.e(TAG, "onChildClick(). This method should be overriden!");
+ return false;
+ }
+
+}
diff --git a/src/com/owncloud/android/ui/fragment/ExtendedListFragment.java b/src/com/owncloud/android/ui/fragment/ExtendedListFragment.java
index 937a50d0b6e..d8de60cc145 100644
--- a/src/com/owncloud/android/ui/fragment/ExtendedListFragment.java
+++ b/src/com/owncloud/android/ui/fragment/ExtendedListFragment.java
@@ -45,25 +45,23 @@
import third_parties.in.srain.cube.GridViewWithHeaderAndFooter;
-/**
- * TODO extending SherlockListFragment instead of SherlockFragment
- */
public class ExtendedListFragment extends Fragment
implements OnItemClickListener, OnEnforceableRefreshListener {
- private static final String TAG = ExtendedListFragment.class.getSimpleName();
+ protected static final String TAG = ExtendedListFragment.class.getSimpleName();
+
+ protected static final String KEY_SAVED_LIST_POSITION = "SAVED_LIST_POSITION";
- private static final String KEY_SAVED_LIST_POSITION = "SAVED_LIST_POSITION";
private static final String KEY_INDEXES = "INDEXES";
private static final String KEY_FIRST_POSITIONS= "FIRST_POSITIONS";
private static final String KEY_TOPS = "TOPS";
private static final String KEY_HEIGHT_CELL = "HEIGHT_CELL";
private static final String KEY_EMPTY_LIST_MESSAGE = "EMPTY_LIST_MESSAGE";
- private SwipeRefreshLayout mRefreshListLayout;
+ protected SwipeRefreshLayout mRefreshListLayout;
private SwipeRefreshLayout mRefreshGridLayout;
- private SwipeRefreshLayout mRefreshEmptyLayout;
- private TextView mEmptyListMessage;
+ protected SwipeRefreshLayout mRefreshEmptyLayout;
+ protected TextView mEmptyListMessage;
// Save the state of the scroll in browsing
private ArrayList mIndexes;
@@ -337,7 +335,7 @@ public String getEmptyViewText() {
return (mEmptyListMessage != null) ? mEmptyListMessage.getText().toString() : "";
}
- private void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) {
+ protected void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) {
// Colors in animations
refreshLayout.setColorSchemeResources(R.color.color_accent, R.color.primary,
R.color.primary_dark);
diff --git a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java
index 68ee6771fea..89651ecaeb3 100644
--- a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java
+++ b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java
@@ -43,7 +43,7 @@
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.FileMenuFilter;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder;
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.services.observer.FileObserverService;
@@ -483,6 +483,8 @@ public void listenForTransferProgress() {
mContainerActivity.getFileUploaderBinder().
addDatatransferProgressListener(mProgressListener, mAccount, getFile());
}
+ } else {
+ Log_OC.d(TAG, "mProgressListener == null");
}
}
diff --git a/src/com/owncloud/android/ui/fragment/FileFragment.java b/src/com/owncloud/android/ui/fragment/FileFragment.java
index fd5aeefa523..19722b266af 100644
--- a/src/com/owncloud/android/ui/fragment/FileFragment.java
+++ b/src/com/owncloud/android/ui/fragment/FileFragment.java
@@ -26,7 +26,7 @@
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder;
import com.owncloud.android.ui.activity.ComponentsGetter;
diff --git a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java
index fbb209e5992..3cd6b691a64 100644
--- a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java
+++ b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java
@@ -27,6 +27,7 @@
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
+import android.os.Parcelable;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.ContextMenu;
import android.view.Menu;
@@ -391,7 +392,7 @@ public boolean onFileActionChosen(int menuId, int filePosition) {
Intent action = new Intent(getActivity(), FolderPickerActivity.class);
// Pass mTargetFile that contains info of selected file/folder
- action.putExtra(FolderPickerActivity.EXTRA_FILE, mTargetFile);
+ action.putExtra(FolderPickerActivity.EXTRA_FILE, (Parcelable)mTargetFile);
getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_MOVE_FILES);
return true;
}
diff --git a/src/com/owncloud/android/ui/fragment/UploadListFragment.java b/src/com/owncloud/android/ui/fragment/UploadListFragment.java
new file mode 100755
index 00000000000..2ddc5efd076
--- /dev/null
+++ b/src/com/owncloud/android/ui/fragment/UploadListFragment.java
@@ -0,0 +1,196 @@
+/**
+ * ownCloud Android client application
+ *
+ * @author LukeOwncloud
+ * 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 .
+ *
+ */
+package com.owncloud.android.ui.fragment;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.ExpandableListView;
+import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.owncloud.android.R;
+import com.owncloud.android.db.UploadDbObject;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.ui.activity.FileActivity;
+import com.owncloud.android.ui.activity.FileDisplayActivity;
+import com.owncloud.android.ui.adapter.ExpandableUploadListAdapter;
+import com.owncloud.android.utils.UploadUtils;
+
+/**
+ * A Fragment that lists all files and folders in a given LOCAL path.
+ *
+ */
+public class UploadListFragment extends ExpandableListFragment {
+ static private String TAG = UploadListFragment.class.getSimpleName();
+
+ /**
+ * Reference to the Activity which this fragment is attached to. For
+ * callbacks
+ */
+ private UploadListFragment.ContainerActivity mContainerActivity;
+
+ ExpandableUploadListAdapter mAdapter;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View v = super.onCreateView(inflater, container, savedInstanceState);
+ getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+ setMessageForEmptyList(getString(R.string.upload_list_empty));
+ setOnRefreshListener(this);
+ return v;
+ }
+
+ @Override
+ public void onRefresh() {
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ try {
+ mContainerActivity = (ContainerActivity) activity;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(activity.toString() + " must implement "
+ + UploadListFragment.ContainerActivity.class.getSimpleName());
+ }
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ Log_OC.d(TAG, "onActivityCreated() start");
+ super.onActivityCreated(savedInstanceState);
+ mAdapter = new ExpandableUploadListAdapter(getActivity());
+ setListAdapter(mAdapter);
+ mAdapter.setFileActivity(((FileActivity) getActivity()));
+
+ registerForContextMenu(getListView());
+ getListView().setOnCreateContextMenuListener(this);
+ }
+
+ @Override
+ public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
+ boolean handled = false;
+ UploadDbObject uploadDbObject = (UploadDbObject) mAdapter.getChild(groupPosition, childPosition);
+ if (uploadDbObject != null) {
+ // notify the click to container Activity
+ handled = mContainerActivity.onUploadItemClick(uploadDbObject);
+ } else {
+ Log_OC.w(TAG, "Null object in ListAdapter!!");
+ }
+ return handled;
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ MenuInflater inflater = getActivity().getMenuInflater();
+ inflater.inflate(R.menu.upload_actions_menu, menu);
+
+ ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuInfo;
+ int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition);
+ int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
+ UploadDbObject uploadFile = (UploadDbObject) mAdapter.getChild(groupPosition, childPosition);
+ if (UploadUtils.userCanCancelUpload(uploadFile)) {
+ MenuItem item = menu.findItem(R.id.action_remove_upload);
+ if (item != null) {
+ item.setVisible(false);
+ item.setEnabled(false);
+ }
+ } else {
+ MenuItem item = menu.findItem(R.id.action_cancel_upload);
+ if (item != null) {
+ item.setVisible(false);
+ item.setEnabled(false);
+ }
+ }
+ if (!UploadUtils.userCanRetryUpload(uploadFile)) {
+ MenuItem item = menu.findItem(R.id.action_retry_upload);
+ if (item != null) {
+ item.setVisible(false);
+ item.setEnabled(false);
+ }
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected (MenuItem item) {
+ ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) item.getMenuInfo();
+ int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition);
+ int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
+ UploadDbObject uploadFile = (UploadDbObject) mAdapter.getChild(groupPosition, childPosition);
+ switch (item.getItemId()) {
+ case R.id.action_cancel_upload:
+ ((FileActivity) getActivity()).getFileOperationsHelper().cancelTransference(uploadFile.getOCFile());
+ return true;
+ case R.id.action_remove_upload: {
+ ((FileActivity) getActivity()).getFileOperationsHelper().removeUploadFromList(uploadFile);
+ return true;
+ }case R.id.action_retry_upload: {
+ ((FileActivity) getActivity()).getFileOperationsHelper().retryUpload(uploadFile);
+ return true;
+ }case R.id.action_see_details: {
+ Intent showDetailsIntent = new Intent(getActivity(), FileDisplayActivity.class);
+ showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable) uploadFile.getOCFile());
+ showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, uploadFile.getAccount(getActivity()));
+ startActivity(showDetailsIntent);
+ return true;
+ }
+ case R.id.action_open_file_with: {
+ ((FileActivity) getActivity()).getFileOperationsHelper().openFile(uploadFile.getOCFile());
+ return true;
+ }
+ default:
+ return super.onContextItemSelected(item);
+ }
+ }
+
+ /**
+ * Interface to implement by any Activity that includes some instance of
+ * UploadListFragment
+ *
+ * @author LukeOwncloud
+ */
+ public interface ContainerActivity {
+
+ /**
+ * Callback method invoked when an upload item is clicked by the user on
+ * the upload list
+ *
+ * @param file
+ * @return return true if click was handled.
+ */
+ public boolean onUploadItemClick(UploadDbObject file);
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/owncloud/android/ui/preview/PreviewImageActivity.java b/src/com/owncloud/android/ui/preview/PreviewImageActivity.java
index 5cbacfcf2cd..8c9bf645ce6 100644
--- a/src/com/owncloud/android/ui/preview/PreviewImageActivity.java
+++ b/src/com/owncloud/android/ui/preview/PreviewImageActivity.java
@@ -32,6 +32,8 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
+import android.os.Parcelable;
+import android.preference.PreferenceManager;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.DrawerLayout;
@@ -47,8 +49,8 @@
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.files.services.FileUploadService;
+import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder;
import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
@@ -300,7 +302,7 @@ public void onServiceConnected(ComponentName component, IBinder service) {
}
} else if (component.equals(new ComponentName(PreviewImageActivity.this,
- FileUploader.class))) {
+ FileUploadService.class))) {
Log_OC.d(TAG, "Upload service connected");
mUploaderBinder = (FileUploaderBinder) service;
} else {
@@ -316,7 +318,7 @@ public void onServiceDisconnected(ComponentName component) {
Log_OC.d(TAG, "Download service suddenly disconnected");
mDownloaderBinder = null;
} else if (component.equals(new ComponentName(PreviewImageActivity.this,
- FileUploader.class))) {
+ FileUploadService.class))) {
Log_OC.d(TAG, "Upload service suddenly disconnected");
mUploaderBinder = null;
}
@@ -391,7 +393,7 @@ private void backToDisplayActivity() {
public void showDetails(OCFile file) {
Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class);
showDetailsIntent.setAction(FileDisplayActivity.ACTION_DETAILS);
- showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, file);
+ showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable)file);
showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT,
AccountUtils.getCurrentOwnCloudAccount(this));
startActivity(showDetailsIntent);
@@ -408,7 +410,7 @@ private void requestForDownload(OCFile file) {
} else if (!mDownloaderBinder.isDownloading(getAccount(), file)) {
Intent i = new Intent(this, FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_ACCOUNT, getAccount());
- i.putExtra(FileDownloader.EXTRA_FILE, file);
+ i.putExtra(FileDownloader.EXTRA_FILE, (Parcelable)file);
startService(i);
}
}
diff --git a/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java b/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java
index b197a855a24..1ba5858a31b 100644
--- a/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java
+++ b/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java
@@ -40,6 +40,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Parcelable;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -558,7 +559,7 @@ public boolean onTouch(View v, MotionEvent event) {
private void startFullScreenVideo() {
Intent i = new Intent(getActivity(), PreviewVideoActivity.class);
i.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount);
- i.putExtra(FileActivity.EXTRA_FILE, getFile());
+ i.putExtra(FileActivity.EXTRA_FILE, (Parcelable)getFile());
i.putExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, mVideoPreview.isPlaying());
mVideoPreview.pause();
i.putExtra(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPreview.getCurrentPosition());
diff --git a/src/com/owncloud/android/utils/FileStorageUtils.java b/src/com/owncloud/android/utils/FileStorageUtils.java
index e70302fc2eb..62deabc4444 100644
--- a/src/com/owncloud/android/utils/FileStorageUtils.java
+++ b/src/com/owncloud/android/utils/FileStorageUtils.java
@@ -30,6 +30,7 @@
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.db.UploadDbObject;
import com.owncloud.android.lib.resources.files.RemoteFile;
import android.annotation.SuppressLint;
@@ -55,22 +56,51 @@ public class FileStorageUtils {
//private static final String LOG_TAG = "FileStorageUtils";
+ /**
+ * Takes a full path to owncloud file and removes beginning which is path to ownload data folder.
+ * If fullPath does not start with that folder, fullPath is returned as is.
+ */
+ public static final String removeDataFolderPath(String fullPath) {
+ File sdCard = Environment.getExternalStorageDirectory();
+ String dataFolderPath = sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/";
+ if(fullPath.indexOf(dataFolderPath) == 0) {
+ return fullPath.substring(dataFolderPath.length());
+ }
+ return fullPath;
+ }
+
+ /**
+ * Get local owncloud storage path for accountName.
+ */
public static final String getSavePath(String accountName) {
File sdCard = Environment.getExternalStorageDirectory();
return sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/" + Uri.encode(accountName, "@");
// URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B
}
+ /**
+ * Get local path where OCFile file is to be stored after upload. That is,
+ * corresponding local path (in local owncloud storage) to remote uploaded
+ * file.
+ */
public static final String getDefaultSavePathFor(String accountName, OCFile file) {
return getSavePath(accountName) + file.getRemotePath();
}
+ /**
+ * Get absolute path to tmp folder inside datafolder in sd-card for given accountName.
+ */
public static final String getTemporalPath(String accountName) {
File sdCard = Environment.getExternalStorageDirectory();
return sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/tmp/" + Uri.encode(accountName, "@");
// URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B
}
+ /**
+ * Optimistic number of bytes available on sd-card. accountName is ignored.
+ * @param accountName not used. can thus be null.
+ * @return Optimistic number of available bytes (can be less)
+ */
@SuppressLint("NewApi")
public static final long getUsableSpace(String accountName) {
File savePath = Environment.getExternalStorageDirectory();
diff --git a/src/com/owncloud/android/utils/UploadUtils.java b/src/com/owncloud/android/utils/UploadUtils.java
new file mode 100755
index 00000000000..dcd3597d115
--- /dev/null
+++ b/src/com/owncloud/android/utils/UploadUtils.java
@@ -0,0 +1,68 @@
+package com.owncloud.android.utils;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo.State;
+import android.os.BatteryManager;
+
+import com.owncloud.android.db.UploadDbObject;
+
+
+public class UploadUtils {
+
+ public static boolean isCharging(Context context) {
+ IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+ Intent batteryStatus = context.registerReceiver(null, ifilter);
+
+ int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
+ return status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;
+ }
+
+ public static boolean isOnline(Context context) {
+ ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected();
+ }
+
+ public static boolean isConnectedViaWiFi(Context context) {
+ ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ return cm != null && cm.getActiveNetworkInfo() != null
+ && cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI
+ && cm.getActiveNetworkInfo().getState() == State.CONNECTED;
+ }
+
+ /**
+ * Returns true when user is able to cancel this upload. That is, when
+ * upload is currently in progress or scheduled for upload.
+ */
+ static public boolean userCanCancelUpload(UploadDbObject uploadFile) {
+ switch (uploadFile.getUploadStatus()) {
+ case UPLOAD_IN_PROGRESS:
+ case UPLOAD_LATER:
+ case UPLOAD_FAILED_RETRY:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Returns true when user can choose to retry this upload. That is, when
+ * user cancelled upload before or when upload has failed.
+ */
+ static public boolean userCanRetryUpload(UploadDbObject uploadFile) {
+ switch (uploadFile.getUploadStatus()) {
+ case UPLOAD_CANCELLED:
+ case UPLOAD_FAILED_RETRY://automatically retried. no need for user option.
+ case UPLOAD_FAILED_GIVE_UP: //TODO this case needs to be handled as described by
+ // https://github.com/owncloud/android/issues/765#issuecomment-66490312
+ case UPLOAD_LATER: //upload is already schedule but allow user to increase priority
+ case UPLOAD_SUCCEEDED: // if user wants let him to re-upload (maybe
+ // remote file was deleted...)
+ return true;
+ default:
+ return false;
+ }
+ }
+}