From: masensio Date: Tue, 29 Oct 2013 13:10:42 +0000 (+0100) Subject: Update packageName X-Git-Tag: oc-android-1.5.5~139^2~15 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/6ace8ab1c18b23a4c016986256075cfda8e50104?ds=inline;hp=--cc Update packageName --- 6ace8ab1c18b23a4c016986256075cfda8e50104 diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0b2e9100..f448d533 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . --> - @@ -153,8 +153,8 @@ - - + + diff --git a/oc_jb_workaround/AndroidManifest.xml b/oc_jb_workaround/AndroidManifest.xml index 31ec731d..c987c824 100644 --- a/oc_jb_workaround/AndroidManifest.xml +++ b/oc_jb_workaround/AndroidManifest.xml @@ -1,6 +1,6 @@ diff --git a/oc_jb_workaround/src/com/owncloud/android/workaround/accounts/AccountAuthenticatorService.java b/oc_jb_workaround/src/com/owncloud/android/workaround/accounts/AccountAuthenticatorService.java new file mode 100644 index 00000000..5a7c57e6 --- /dev/null +++ b/oc_jb_workaround/src/com/owncloud/android/workaround/accounts/AccountAuthenticatorService.java @@ -0,0 +1,138 @@ +/* ownCloud Jelly Bean Workaround for lost credentials + * Copyright (C) 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.workaround.accounts; + +import android.accounts.AbstractAccountAuthenticator; +import android.accounts.Account; +import android.accounts.AccountAuthenticatorResponse; +import android.accounts.AccountManager; +import android.accounts.NetworkErrorException; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +//import android.util.Log; + +public class AccountAuthenticatorService extends Service { + + private AccountAuthenticator mAuthenticator; + //static final public String ACCOUNT_TYPE = "owncloud"; + + @Override + public void onCreate() { + super.onCreate(); + mAuthenticator = new AccountAuthenticator(this); + } + + @Override + public IBinder onBind(Intent intent) { + return mAuthenticator.getIBinder(); + } + + + public static class AccountAuthenticator extends AbstractAccountAuthenticator { + + public static final String KEY_AUTH_TOKEN_TYPE = "authTokenType"; + public static final String KEY_REQUIRED_FEATURES = "requiredFeatures"; + public static final String KEY_LOGIN_OPTIONS = "loginOptions"; + + public AccountAuthenticator(Context context) { + super(context); + } + + @Override + public Bundle addAccount(AccountAuthenticatorResponse response, + String accountType, String authTokenType, + String[] requiredFeatures, Bundle options) + throws NetworkErrorException { + //Log.e("WORKAROUND", "Yes, WORKAROUND takes the control here"); + final Intent intent = new Intent("com.owncloud.android.workaround.accounts.CREATE"); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, + response); + intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); + intent.putExtra(KEY_REQUIRED_FEATURES, requiredFeatures); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); + intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + intent.addFlags(Intent.FLAG_FROM_BACKGROUND); + + final Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return bundle; + //return getCommonResultBundle(); + } + + + @Override + public Bundle confirmCredentials(AccountAuthenticatorResponse response, + Account account, Bundle options) throws NetworkErrorException { + return getCommonResultBundle(); + } + + @Override + public Bundle editProperties(AccountAuthenticatorResponse response, + String accountType) { + return getCommonResultBundle(); + } + + @Override + public Bundle getAuthToken(AccountAuthenticatorResponse response, + Account account, String authTokenType, Bundle options) + throws NetworkErrorException { + return getCommonResultBundle(); + } + + @Override + public String getAuthTokenLabel(String authTokenType) { + return ""; + } + + @Override + public Bundle hasFeatures(AccountAuthenticatorResponse response, + Account account, String[] features) throws NetworkErrorException { + return getCommonResultBundle(); + } + + @Override + public Bundle updateCredentials(AccountAuthenticatorResponse response, + Account account, String authTokenType, Bundle options) + throws NetworkErrorException { + return getCommonResultBundle(); + } + + @Override + public Bundle getAccountRemovalAllowed( + AccountAuthenticatorResponse response, Account account) + throws NetworkErrorException { + return super.getAccountRemovalAllowed(response, account); + } + + private Bundle getCommonResultBundle() { + Bundle resultBundle = new Bundle(); + resultBundle.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION); + resultBundle.putString(AccountManager.KEY_ERROR_MESSAGE, "This is just a workaround, not a real account authenticator"); + return resultBundle; + } + + } +} diff --git a/oc_jb_workaround/src/de/mobilcom/debitel/cloud/android/workaround/accounts/AccountAuthenticatorService.java b/oc_jb_workaround/src/de/mobilcom/debitel/cloud/android/workaround/accounts/AccountAuthenticatorService.java deleted file mode 100644 index e8e4933b..00000000 --- a/oc_jb_workaround/src/de/mobilcom/debitel/cloud/android/workaround/accounts/AccountAuthenticatorService.java +++ /dev/null @@ -1,138 +0,0 @@ -/* ownCloud Jelly Bean Workaround for lost credentials - * Copyright (C) 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 de.mobilcom.debitel.cloud.android.workaround.accounts; - -import android.accounts.AbstractAccountAuthenticator; -import android.accounts.Account; -import android.accounts.AccountAuthenticatorResponse; -import android.accounts.AccountManager; -import android.accounts.NetworkErrorException; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.IBinder; -//import android.util.Log; - -public class AccountAuthenticatorService extends Service { - - private AccountAuthenticator mAuthenticator; - //static final public String ACCOUNT_TYPE = "owncloud"; - - @Override - public void onCreate() { - super.onCreate(); - mAuthenticator = new AccountAuthenticator(this); - } - - @Override - public IBinder onBind(Intent intent) { - return mAuthenticator.getIBinder(); - } - - - public static class AccountAuthenticator extends AbstractAccountAuthenticator { - - public static final String KEY_AUTH_TOKEN_TYPE = "authTokenType"; - public static final String KEY_REQUIRED_FEATURES = "requiredFeatures"; - public static final String KEY_LOGIN_OPTIONS = "loginOptions"; - - public AccountAuthenticator(Context context) { - super(context); - } - - @Override - public Bundle addAccount(AccountAuthenticatorResponse response, - String accountType, String authTokenType, - String[] requiredFeatures, Bundle options) - throws NetworkErrorException { - //Log.e("WORKAROUND", "Yes, WORKAROUND takes the control here"); - final Intent intent = new Intent("de.mobilcom.debitel.cloud.android.workaround.accounts.CREATE"); - intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, - response); - intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); - intent.putExtra(KEY_REQUIRED_FEATURES, requiredFeatures); - intent.putExtra(KEY_LOGIN_OPTIONS, options); - - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); - intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - intent.addFlags(Intent.FLAG_FROM_BACKGROUND); - - final Bundle bundle = new Bundle(); - bundle.putParcelable(AccountManager.KEY_INTENT, intent); - return bundle; - //return getCommonResultBundle(); - } - - - @Override - public Bundle confirmCredentials(AccountAuthenticatorResponse response, - Account account, Bundle options) throws NetworkErrorException { - return getCommonResultBundle(); - } - - @Override - public Bundle editProperties(AccountAuthenticatorResponse response, - String accountType) { - return getCommonResultBundle(); - } - - @Override - public Bundle getAuthToken(AccountAuthenticatorResponse response, - Account account, String authTokenType, Bundle options) - throws NetworkErrorException { - return getCommonResultBundle(); - } - - @Override - public String getAuthTokenLabel(String authTokenType) { - return ""; - } - - @Override - public Bundle hasFeatures(AccountAuthenticatorResponse response, - Account account, String[] features) throws NetworkErrorException { - return getCommonResultBundle(); - } - - @Override - public Bundle updateCredentials(AccountAuthenticatorResponse response, - Account account, String authTokenType, Bundle options) - throws NetworkErrorException { - return getCommonResultBundle(); - } - - @Override - public Bundle getAccountRemovalAllowed( - AccountAuthenticatorResponse response, Account account) - throws NetworkErrorException { - return super.getAccountRemovalAllowed(response, account); - } - - private Bundle getCommonResultBundle() { - Bundle resultBundle = new Bundle(); - resultBundle.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION); - resultBundle.putString(AccountManager.KEY_ERROR_MESSAGE, "This is just a workaround, not a real account authenticator"); - return resultBundle; - } - - } -} diff --git a/pom.xml b/pom.xml index e13b7ab2..a55a77a1 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - de.mobilcom.debitel.cloud.android + com.owncloud.android owncloud 1.3.21-SNAPSHOT apk diff --git a/res/layout-land/account_setup.xml b/res/layout-land/account_setup.xml index b0a42ae6..3500c0fd 100644 --- a/res/layout-land/account_setup.xml +++ b/res/layout-land/account_setup.xml @@ -185,7 +185,7 @@ - - - - - - - - - - - - - - - - - - - - + class="com.owncloud.android.ui.fragment.LocalFileListFragment" /> - - - diff --git a/res/raw-de/changelog.html b/res/raw-de/changelog.html index 7f6b7464..a54a6643 100644 --- a/res/raw-de/changelog.html +++ b/res/raw-de/changelog.html @@ -24,7 +24,7 @@ In dieser Version von Android existiert ein Bug, der nach jedem Neustart eine erneute Eingabe der ownCloud Login-Informationen nötig macht. Um das zu umgehen installieren Sie bitte diese kostenlose Hilfs-App:

- ownCloud Jelly Bean Workaround + ownCloud Jelly Bean Workaround

diff --git a/res/raw-es/changelog.html b/res/raw-es/changelog.html index 0802d390..9321d520 100644 --- a/res/raw-es/changelog.html +++ b/res/raw-es/changelog.html @@ -24,7 +24,7 @@ Para prevenir la pérdida de las credenciales de sus cuentas ownCloud en cada reinicio, por favor, instale esta app gratuita que evita el problema en Jelly Bean:

- ownCloud Jelly Bean Workaround + ownCloud Jelly Bean Workaround

diff --git a/res/raw/changelog.html b/res/raw/changelog.html index f388f0fc..754cf6f2 100644 --- a/res/raw/changelog.html +++ b/res/raw/changelog.html @@ -24,7 +24,7 @@ To prevent losing your ownCloud account credentials on every reboot, please, install this free helper app to work around the bug in Jelly Bean:

- ownCloud Jelly Bean Workaround + ownCloud Jelly Bean Workaround

diff --git a/res/values/oauth2_configuration.xml b/res/values/oauth2_configuration.xml index c81f54ac..f8e0f51c 100644 --- a/res/values/oauth2_configuration.xml +++ b/res/values/oauth2_configuration.xml @@ -12,7 +12,7 @@ code - de.mobilcom.debitel.cloud.android + com.owncloud.android diff --git a/res/values/setup.xml b/res/values/setup.xml index 4b497015..e2460d95 100644 --- a/res/values/setup.xml +++ b/res/values/setup.xml @@ -39,7 +39,7 @@ "https://webapi.md.de/about/imprint" "mailto:" "mailto:appservice@cloud.md.de" - + diff --git a/src/com/owncloud/android/DisplayUtils.java b/src/com/owncloud/android/DisplayUtils.java new file mode 100644 index 00000000..1ee898b3 --- /dev/null +++ b/src/com/owncloud/android/DisplayUtils.java @@ -0,0 +1,190 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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; + +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * A helper class for some string operations. + * + * @author Bartek Przybylski + * @author David A. Velasco + */ +public class DisplayUtils { + + //private static String TAG = DisplayUtils.class.getSimpleName(); + + private static final String[] sizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; + + private static HashMap mimeType2HUmanReadable; + static { + mimeType2HUmanReadable = new HashMap(); + // images + mimeType2HUmanReadable.put("image/jpeg", "JPEG image"); + mimeType2HUmanReadable.put("image/jpg", "JPEG image"); + mimeType2HUmanReadable.put("image/png", "PNG image"); + mimeType2HUmanReadable.put("image/bmp", "Bitmap image"); + mimeType2HUmanReadable.put("image/gif", "GIF image"); + mimeType2HUmanReadable.put("image/svg+xml", "JPEG image"); + mimeType2HUmanReadable.put("image/tiff", "TIFF image"); + // music + mimeType2HUmanReadable.put("audio/mpeg", "MP3 music file"); + mimeType2HUmanReadable.put("application/ogg", "OGG music file"); + + } + + private static final String TYPE_APPLICATION = "application"; + private static final String TYPE_AUDIO = "audio"; + private static final String TYPE_IMAGE = "image"; + private static final String TYPE_TXT = "text"; + private static final String TYPE_VIDEO = "video"; + + private static final String SUBTYPE_PDF = "pdf"; + private static final String[] SUBTYPES_DOCUMENT = { "msword", "mspowerpoint", "msexcel", + "vnd.oasis.opendocument.presentation", + "vnd.oasis.opendocument.spreadsheet", + "vnd.oasis.opendocument.text" + }; + private static Set SUBTYPES_DOCUMENT_SET = new HashSet(Arrays.asList(SUBTYPES_DOCUMENT)); + private static final String[] SUBTYPES_COMPRESSED = {"x-tar", "x-gzip", "zip"}; + private static final Set SUBTYPES_COMPRESSED_SET = new HashSet(Arrays.asList(SUBTYPES_COMPRESSED)); + + /** + * Converts the file size in bytes to human readable output. + * + * @param bytes Input file size + * @return Like something readable like "12 MB" + */ + public static String bytesToHumanReadable(long bytes) { + double result = bytes; + int attachedsuff = 0; + while (result > 1024 && attachedsuff < sizeSuffixes.length) { + result /= 1024.; + attachedsuff++; + } + result = ((int) (result * 100)) / 100.; + return result + " " + sizeSuffixes[attachedsuff]; + } + + /** + * Removes special HTML entities from a string + * + * @param s Input string + * @return A cleaned version of the string + */ + public static String HtmlDecode(String s) { + /* + * TODO: Perhaps we should use something more proven like: + * http://commons.apache.org/lang/api-2.6/org/apache/commons/lang/StringEscapeUtils.html#unescapeHtml%28java.lang.String%29 + */ + + String ret = ""; + for (int i = 0; i < s.length(); ++i) { + if (s.charAt(i) == '%') { + ret += (char) Integer.parseInt(s.substring(i + 1, i + 3), 16); + i += 2; + } else { + ret += s.charAt(i); + } + } + return ret; + } + + /** + * Converts MIME types like "image/jpg" to more end user friendly output + * like "JPG image". + * + * @param mimetype MIME type to convert + * @return A human friendly version of the MIME type + */ + public static String convertMIMEtoPrettyPrint(String mimetype) { + if (mimeType2HUmanReadable.containsKey(mimetype)) { + return mimeType2HUmanReadable.get(mimetype); + } + if (mimetype.split("/").length >= 2) + return mimetype.split("/")[1].toUpperCase() + " file"; + return "Unknown type"; + } + + + /** + * Returns the resource identifier of an image resource to use as icon associated to a + * known MIME type. + * + * @param mimetype MIME type string. + * @return Resource identifier of an image resource. + */ + public static int getResourceId(String mimetype) { + + if (mimetype == null || "DIR".equals(mimetype)) { + return R.drawable.ic_menu_archive; + + } else { + String [] parts = mimetype.split("/"); + String type = parts[0]; + String subtype = (parts.length > 1) ? parts[1] : ""; + + if(TYPE_TXT.equals(type)) { + return R.drawable.file_doc; + + } else if(TYPE_IMAGE.equals(type)) { + return R.drawable.file_image; + + } else if(TYPE_VIDEO.equals(type)) { + return R.drawable.file_movie; + + } else if(TYPE_AUDIO.equals(type)) { + return R.drawable.file_sound; + + } else if(TYPE_APPLICATION.equals(type)) { + + if (SUBTYPE_PDF.equals(subtype)) { + return R.drawable.file_pdf; + + } else if (SUBTYPES_DOCUMENT_SET.contains(subtype)) { + return R.drawable.file_doc; + + } else if (SUBTYPES_COMPRESSED_SET.contains(subtype)) { + return R.drawable.file_zip; + } + + } + // problems: RAR, RTF, 3GP are send as application/octet-stream from the server ; extension in the filename should be explicitly reviewed + } + + // default icon + return R.drawable.file; + } + + + + /** + * Converts Unix time to human readable format + * @param miliseconds that have passed since 01/01/1970 + * @return The human readable time for the users locale + */ + public static String unixTimeToHumanReadable(long milliseconds) { + Date date = new Date(milliseconds); + return date.toLocaleString(); + } +} diff --git a/src/com/owncloud/android/Log_OC.java b/src/com/owncloud/android/Log_OC.java new file mode 100644 index 00000000..5a1be298 --- /dev/null +++ b/src/com/owncloud/android/Log_OC.java @@ -0,0 +1,127 @@ +package com.owncloud.android; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +import android.util.Log; + + + +public class Log_OC { + + + private static boolean isEnabled = false; + private static File logFile; + private static File folder; + private static BufferedWriter buf; + + public static void i(String TAG, String message){ + // Printing the message to LogCat console + Log.i(TAG, message); + // Write the log message to the file + appendLog(TAG+" : "+message); + } + + public static void d(String TAG, String message){ + Log.d(TAG, message); + appendLog(TAG + " : " + message); + } + public static void d(String TAG, String message, Exception e) { + Log.d(TAG, message, e); + appendLog(TAG + " : " + message + " Exception : "+ e.getStackTrace()); + } + public static void e(String TAG, String message){ + Log.e(TAG, message); + appendLog(TAG + " : " + message); + } + + public static void e(String TAG, String message, Throwable e) { + Log.e(TAG, message, e); + appendLog(TAG+" : " + message +" Exception : " + e.getStackTrace()); + } + + public static void v(String TAG, String message){ + Log.v(TAG, message); + appendLog(TAG+" : "+ message); + } + + public static void w(String TAG, String message) { + Log.w(TAG,message); + appendLog(TAG+" : "+ message); + } + + public static void wtf(String TAG, String message) { + Log.wtf(TAG,message); + appendLog(TAG+" : "+ message); + } + + public static void startLogging(String logPath) { + folder = new File(logPath); + logFile = new File(folder + File.separator + "log.txt"); + + if (!folder.exists()) { + folder.mkdirs(); + } + if (logFile.exists()) { + logFile.delete(); + } + try { + logFile.createNewFile(); + buf = new BufferedWriter(new FileWriter(logFile, true)); + isEnabled = true; + appendPhoneInfo(); + }catch (IOException e){ + e.printStackTrace(); + } + } + + public static void stopLogging() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()); + String currentDateandTime = sdf.format(new Date()); + if (logFile != null) { + logFile.renameTo(new File(folder + File.separator + MainApp.getLogName() + currentDateandTime+".log")); + + isEnabled = false; + try { + buf.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + } + + private static void appendPhoneInfo() { + appendLog("Model : " + android.os.Build.MODEL); + appendLog("Brand : " + android.os.Build.BRAND); + appendLog("Product : " + android.os.Build.PRODUCT); + appendLog("Device : " + android.os.Build.DEVICE); + appendLog("Version-Codename : " + android.os.Build.VERSION.CODENAME); + appendLog("Version-Release : " + android.os.Build.VERSION.RELEASE); + } + + private static void appendLog(String text) { + if (isEnabled) { + try { + buf.append(text); + buf.newLine(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} + + + + + + + + +} diff --git a/src/com/owncloud/android/MainApp.java b/src/com/owncloud/android/MainApp.java new file mode 100644 index 00000000..6cd88fe1 --- /dev/null +++ b/src/com/owncloud/android/MainApp.java @@ -0,0 +1,105 @@ +/* ownCloud Android client application + * 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; + +import android.app.Application; +import android.content.Context; +/** + * Main Application of the project + * + * Contains methods to build the "static" strings. These strings were before constants in different classes + * + * @author masensio + */ +public class MainApp extends Application { + + private static Context mContext; + + public void onCreate(){ + super.onCreate(); + MainApp.mContext = getApplicationContext(); + } + + public static Context getAppContext() { + return MainApp.mContext; + } + + // Methods to obtain Strings referring app_name + // From AccountAuthenticator + // public static final String ACCOUNT_TYPE = "owncloud"; + public static String getAccountType() { + return getAppContext().getResources().getString(R.string.account_type); + } + + // From AccountAuthenticator + // public static final String AUTHORITY = "org.owncloud"; + public static String getAuthority() { + return getAppContext().getResources().getString(R.string.authority); + } + + // From AccountAuthenticator + // public static final String AUTH_TOKEN_TYPE = "org.owncloud"; + public static String getAuthTokenType() { + return getAppContext().getResources().getString(R.string.authority); + } + + // From AccountAuthenticator + // public static final String AUTH_TOKEN_TYPE_PASSWORD = "owncloud.password"; + public static String getAuthTokenTypePass() { + return getAppContext().getResources().getString(R.string.account_type) + ".password"; + } + + // From AccountAuthenticator + // public static final String AUTH_TOKEN_TYPE_ACCESS_TOKEN = "owncloud.oauth2.access_token"; + public static String getAuthTokenTypeAccessToken() { + return getAppContext().getResources().getString(R.string.account_type) + ".oauth2.access_token"; + } + + // From AccountAuthenticator + // public static final String AUTH_TOKEN_TYPE_REFRESH_TOKEN = "owncloud.oauth2.refresh_token"; + public static String getAuthTokenTypeRefreshToken() { + return getAppContext().getResources().getString(R.string.account_type) + ".oauth2.refresh_token"; + } + + // From AccountAuthenticator + // public static final String AUTH_TOKEN_TYPE_SAML_WEB_SSO_SESSION_COOKIE = "owncloud.saml.web_sso.session_cookie"; + public static String getAuthTokenTypeSamlSessionCookie() { + return getAppContext().getResources().getString(R.string.account_type) + ".saml.web_sso.session_cookie"; + } + + // From ProviderMeta + // public static final String DB_FILE = "owncloud.db"; + public static String getDBFile() { + return getAppContext().getResources().getString(R.string.db_file); + } + + // From ProviderMeta + // private final String mDatabaseName = "ownCloud"; + public static String getDBName() { + return getAppContext().getResources().getString(R.string.db_name); + } + + // data_folder + public static String getDataFolder() { + return getAppContext().getResources().getString(R.string.data_folder); + } + + // log_name + public static String getLogName() { + return getAppContext().getResources().getString(R.string.log_name); + } +} diff --git a/src/com/owncloud/android/OwnCloudSession.java b/src/com/owncloud/android/OwnCloudSession.java new file mode 100644 index 00000000..d7bb6094 --- /dev/null +++ b/src/com/owncloud/android/OwnCloudSession.java @@ -0,0 +1,56 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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; + +/** + * Represents a session to an ownCloud instance + * + * @author Bartek Przybylski + * + */ +public class OwnCloudSession { + private String mSessionName; + private String mSessionUrl; + private int mEntryId; + + public OwnCloudSession(String name, String url, int entryId) { + mSessionName = name; + mSessionUrl = url; + mEntryId = entryId; + } + + public void setName(String name) { + mSessionName = name; + } + + public String getName() { + return mSessionName; + } + + public void setUrl(String url) { + mSessionUrl = url; + } + + public String getUrl() { + return mSessionUrl; + } + + public int getEntryId() { + return mEntryId; + } +} diff --git a/src/com/owncloud/android/Uploader.java b/src/com/owncloud/android/Uploader.java new file mode 100644 index 00000000..ddd3f4ca --- /dev/null +++ b/src/com/owncloud/android/Uploader.java @@ -0,0 +1,429 @@ +/* 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; + +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 com.owncloud.android.R; +import com.owncloud.android.authentication.AccountAuthenticator; +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.ui.CustomButton; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.Dialog; +import android.app.ListActivity; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnCancelListener; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcelable; +import android.provider.MediaStore.Audio; +import android.provider.MediaStore.Images; +import android.provider.MediaStore.Video; +import android.view.View; +import android.view.Window; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.EditText; +import android.widget.SimpleAdapter; +import android.widget.Toast; + + + +/** + * This can be used to upload things to an ownCloud instance. + * + * @author Bartek Przybylski + * + */ +public class Uploader extends ListActivity implements OnItemClickListener, android.view.View.OnClickListener { + private static final String TAG = "ownCloudUploader"; + + private Account mAccount; + private AccountManager mAccountManager; + private Stack mParents; + private ArrayList mStreamsToUpload; + private boolean mCreateDir; + private String mUploadPath; + private DataStorageManager mStorageManager; + private OCFile mFile; + + private final static int DIALOG_NO_ACCOUNT = 0; + private final static int DIALOG_WAITING = 1; + private final static int DIALOG_NO_STREAM = 2; + private final static int DIALOG_MULTIPLE_ACCOUNT = 3; + + private final static int REQUEST_CODE_SETUP_ACCOUNT = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().requestFeature(Window.FEATURE_NO_TITLE); + mParents = new Stack(); + mParents.add(""); + if (prepareStreamsToUpload()) { + mAccountManager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE); + Account[] accounts = mAccountManager.getAccountsByType(MainApp.getAccountType()); + if (accounts.length == 0) { + Log_OC.i(TAG, "No ownCloud account is available"); + showDialog(DIALOG_NO_ACCOUNT); + } else if (accounts.length > 1) { + Log_OC.i(TAG, "More then one ownCloud is available"); + showDialog(DIALOG_MULTIPLE_ACCOUNT); + } else { + mAccount = accounts[0]; + mStorageManager = new FileDataStorageManager(mAccount, getContentResolver()); + populateDirectoryList(); + } + } else { + showDialog(DIALOG_NO_STREAM); + } + } + + @Override + protected Dialog onCreateDialog(final int id) { + final AlertDialog.Builder builder = new Builder(this); + switch (id) { + case DIALOG_WAITING: + ProgressDialog pDialog = new ProgressDialog(this); + pDialog.setIndeterminate(false); + pDialog.setCancelable(false); + pDialog.setMessage(getResources().getString(R.string.uploader_info_uploading)); + return pDialog; + case DIALOG_NO_ACCOUNT: + 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))); + builder.setCancelable(false); + builder.setPositiveButton(R.string.uploader_wrn_no_account_setup_btn_text, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ECLAIR_MR1) { + // using string value since in API7 this + // constatn is not defined + // in API7 < this constatant is defined in + // Settings.ADD_ACCOUNT_SETTINGS + // and Settings.EXTRA_AUTHORITIES + Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT); + intent.putExtra("authorities", new String[] { MainApp.getAuthTokenType() }); + startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT); + } else { + // since in API7 there is no direct call for + // account setup, so we need to + // show our own AccountSetupAcricity, get + // desired results and setup + // everything for ourself + Intent intent = new Intent(getBaseContext(), AccountAuthenticator.class); + startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT); + } + } + }); + builder.setNegativeButton(R.string.uploader_wrn_no_account_quit_btn_text, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + return builder.create(); + case DIALOG_MULTIPLE_ACCOUNT: + CharSequence ac[] = new CharSequence[mAccountManager.getAccountsByType(MainApp.getAccountType()).length]; + for (int i = 0; i < ac.length; ++i) { + ac[i] = mAccountManager.getAccountsByType(MainApp.getAccountType())[i].name; + } + builder.setTitle(R.string.common_choose_account); + builder.setItems(ac, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mAccount = mAccountManager.getAccountsByType(MainApp.getAccountType())[which]; + mStorageManager = new FileDataStorageManager(mAccount, getContentResolver()); + populateDirectoryList(); + } + }); + builder.setCancelable(true); + builder.setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + dialog.cancel(); + finish(); + } + }); + return builder.create(); + case DIALOG_NO_STREAM: + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setTitle(R.string.uploader_wrn_no_content_title); + builder.setMessage(R.string.uploader_wrn_no_content_text); + builder.setCancelable(false); + builder.setNegativeButton(R.string.common_cancel, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + return builder.create(); + default: + throw new IllegalArgumentException("Unknown dialog id: " + id); + } + } + + class a implements OnClickListener { + String mPath; + EditText mDirname; + + public a(String path, EditText dirname) { + mPath = path; + mDirname = dirname; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + Uploader.this.mUploadPath = mPath + mDirname.getText().toString(); + Uploader.this.mCreateDir = true; + uploadFiles(); + } + } + + @Override + public void onBackPressed() { + + if (mParents.size() <= 1) { + super.onBackPressed(); + return; + } else { + mParents.pop(); + populateDirectoryList(); + } + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + // click on folder in the list + Log_OC.d(TAG, "on item click"); + Vector tmpfiles = mStorageManager.getDirectoryContent(mFile); + if (tmpfiles.size() <= 0) return; + // filter on dirtype + Vector files = new Vector(); + for (OCFile f : tmpfiles) + if (f.isDirectory()) + files.add(f); + if (files.size() < position) { + throw new IndexOutOfBoundsException("Incorrect item selected"); + } + mParents.push(files.get(position).getFileName()); + populateDirectoryList(); + } + + @Override + public void onClick(View v) { + // click on button + switch (v.getId()) { + case R.id.uploader_choose_folder: + mUploadPath = ""; // first element in mParents is root dir, represented by ""; init mUploadPath with "/" results in a "//" prefix + for (String p : mParents) + mUploadPath += p + OCFile.PATH_SEPARATOR; + Log_OC.d(TAG, "Uploading file to dir " + mUploadPath); + + uploadFiles(); + + break; + default: + throw new IllegalArgumentException("Wrong element clicked"); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + Log_OC.i(TAG, "result received. req: " + requestCode + " res: " + resultCode); + if (requestCode == REQUEST_CODE_SETUP_ACCOUNT) { + dismissDialog(DIALOG_NO_ACCOUNT); + if (resultCode == RESULT_CANCELED) { + finish(); + } + Account[] accounts = mAccountManager.getAccountsByType(MainApp.getAuthTokenType()); + if (accounts.length == 0) { + showDialog(DIALOG_NO_ACCOUNT); + } else { + // there is no need for checking for is there more then one + // account at this point + // since account setup can set only one account at time + mAccount = accounts[0]; + populateDirectoryList(); + } + } + } + + private void populateDirectoryList() { + setContentView(R.layout.uploader_layout); + + String full_path = ""; + for (String a : mParents) + full_path += a + "/"; + + Log_OC.d(TAG, "Populating view with content of : " + full_path); + + mFile = mStorageManager.getFileByPath(full_path); + if (mFile != null) { + Vector files = mStorageManager.getDirectoryContent(mFile); + List> data = new LinkedList>(); + for (OCFile f : files) { + HashMap h = new HashMap(); + if (f.isDirectory()) { + h.put("dirname", f.getFileName()); + data.add(h); + } + } + SimpleAdapter sa = new SimpleAdapter(this, + data, + R.layout.uploader_list_item_layout, + new String[] {"dirname"}, + new int[] {R.id.textView1}); + setListAdapter(sa); + CustomButton btn = (CustomButton) findViewById(R.id.uploader_choose_folder); + btn.setOnClickListener(this); + getListView().setOnItemClickListener(this); + } + } + + private boolean prepareStreamsToUpload() { + if (getIntent().getAction().equals(Intent.ACTION_SEND)) { + mStreamsToUpload = new ArrayList(); + mStreamsToUpload.add(getIntent().getParcelableExtra(Intent.EXTRA_STREAM)); + } else if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) { + mStreamsToUpload = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM); + } + return (mStreamsToUpload != null && mStreamsToUpload.get(0) != null); + } + + public void uploadFiles() { + try { + //WebdavClient webdav = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext()); + + ArrayList local = new ArrayList(); + ArrayList remote = new ArrayList(); + + /* TODO - mCreateDir can never be true at this moment; we will replace wdc.createDirectory by CreateFolderOperation when that is fixed + WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext()); + // create last directory in path if necessary + if (mCreateDir) { + wdc.createDirectory(mUploadPath); + } + */ + + // this checks the mimeType + for (Parcelable mStream : mStreamsToUpload) { + + Uri uri = (Uri) mStream; + if (uri !=null) { + if (uri.getScheme().equals("content")) { + + String mimeType = getContentResolver().getType(uri); + + if (mimeType.contains("image")) { + String[] CONTENT_PROJECTION = { Images.Media.DATA, Images.Media.DISPLAY_NAME, Images.Media.MIME_TYPE, Images.Media.SIZE}; + Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null); + c.moveToFirst(); + int index = c.getColumnIndex(Images.Media.DATA); + String data = c.getString(index); + local.add(data); + remote.add(mUploadPath + c.getString(c.getColumnIndex(Images.Media.DISPLAY_NAME))); + + } + else if (mimeType.contains("video")) { + String[] CONTENT_PROJECTION = { Video.Media.DATA, Video.Media.DISPLAY_NAME, Video.Media.MIME_TYPE, Video.Media.SIZE, Video.Media.DATE_MODIFIED }; + Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null); + c.moveToFirst(); + int index = c.getColumnIndex(Video.Media.DATA); + String data = c.getString(index); + local.add(data); + remote.add(mUploadPath + c.getString(c.getColumnIndex(Video.Media.DISPLAY_NAME))); + + } + else if (mimeType.contains("audio")) { + String[] CONTENT_PROJECTION = { Audio.Media.DATA, Audio.Media.DISPLAY_NAME, Audio.Media.MIME_TYPE, Audio.Media.SIZE }; + Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null); + c.moveToFirst(); + int index = c.getColumnIndex(Audio.Media.DATA); + String data = c.getString(index); + local.add(data); + remote.add(mUploadPath + c.getString(c.getColumnIndex(Audio.Media.DISPLAY_NAME))); + + } + else { + String filePath = Uri.decode(uri.toString()).replace(uri.getScheme() + "://", ""); + // cut everything whats before mnt. It occured to me that sometimes apps send their name into the URI + if (filePath.contains("mnt")) { + String splitedFilePath[] = filePath.split("/mnt"); + filePath = splitedFilePath[1]; + } + final File file = new File(filePath); + local.add(file.getAbsolutePath()); + remote.add(mUploadPath + file.getName()); + } + + } else if (uri.getScheme().equals("file")) { + String filePath = Uri.decode(uri.toString()).replace(uri.getScheme() + "://", ""); + if (filePath.contains("mnt")) { + String splitedFilePath[] = filePath.split("/mnt"); + filePath = splitedFilePath[1]; + } + final File file = new File(filePath); + local.add(file.getAbsolutePath()); + remote.add(mUploadPath + file.getName()); + } + else { + throw new SecurityException(); + } + } + else { + 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, remote.toArray(new String[remote.size()])); + intent.putExtra(FileUploader.KEY_ACCOUNT, mAccount); + startService(intent); + finish(); + } + + } catch (SecurityException e) { + String message = String.format(getString(R.string.uploader_error_forbidden_content), getString(R.string.app_name)); + Toast.makeText(this, message, Toast.LENGTH_LONG).show(); + } + } + +} diff --git a/src/com/owncloud/android/authentication/AccountAuthenticator.java b/src/com/owncloud/android/authentication/AccountAuthenticator.java new file mode 100644 index 00000000..a2a38e89 --- /dev/null +++ b/src/com/owncloud/android/authentication/AccountAuthenticator.java @@ -0,0 +1,359 @@ +/* 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.authentication; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; +import com.owncloud.android.R; + +import android.accounts.*; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.widget.Toast; + + + + +/** + * Authenticator for ownCloud accounts. + * + * Controller class accessed from the system AccountManager, providing integration of ownCloud accounts with the Android system. + * + * TODO - better separation in operations for OAuth-capable and regular ownCloud accounts. + * TODO - review completeness + * + * @author David A. Velasco + */ +public class AccountAuthenticator extends AbstractAccountAuthenticator { + + /** + * Is used by android system to assign accounts to authenticators. Should be + * used by application and all extensions. + */ + /* These constants are now in MainApp + public static final String ACCOUNT_TYPE = "owncloud"; + public static final String AUTHORITY = "org.owncloud"; + public static final String AUTH_TOKEN_TYPE = "org.owncloud"; + public static final String AUTH_TOKEN_TYPE_PASSWORD = "owncloud.password"; + public static final String AUTH_TOKEN_TYPE_ACCESS_TOKEN = "owncloud.oauth2.access_token"; + public static final String AUTH_TOKEN_TYPE_REFRESH_TOKEN = "owncloud.oauth2.refresh_token"; + public static final String AUTH_TOKEN_TYPE_SAML_WEB_SSO_SESSION_COOKIE = "owncloud.saml.web_sso.session_cookie"; + */ + public static final String KEY_AUTH_TOKEN_TYPE = "authTokenType"; + public static final String KEY_REQUIRED_FEATURES = "requiredFeatures"; + public static final String KEY_LOGIN_OPTIONS = "loginOptions"; + public static final String KEY_ACCOUNT = "account"; + + /** + * Value under this key should handle path to webdav php script. Will be + * removed and usage should be replaced by combining + * {@link com.owncloud.android.authentication.AuthenticatorActivity.KEY_OC_BASE_URL} and + * {@link com.owncloud.android.utils.OwnCloudVersion} + * + * @deprecated + */ + public static final String KEY_OC_URL = "oc_url"; + /** + * Version should be 3 numbers separated by dot so it can be parsed by + * {@link com.owncloud.android.utils.OwnCloudVersion} + */ + public static final String KEY_OC_VERSION = "oc_version"; + /** + * Base url should point to owncloud installation without trailing / ie: + * http://server/path or https://owncloud.server + */ + public static final String KEY_OC_BASE_URL = "oc_base_url"; + /** + * Flag signaling if the ownCloud server can be accessed with OAuth2 access tokens. + */ + public static final String KEY_SUPPORTS_OAUTH2 = "oc_supports_oauth2"; + /** + * Flag signaling if the ownCloud server can be accessed with session cookies from SAML-based web single-sign-on. + */ + public static final String KEY_SUPPORTS_SAML_WEB_SSO = "oc_supports_saml_web_sso"; + + private static final String TAG = AccountAuthenticator.class.getSimpleName(); + + private Context mContext; + + private Handler mHandler; + + public AccountAuthenticator(Context context) { + super(context); + mContext = context; + mHandler = new Handler(); + } + + /** + * {@inheritDoc} + */ + @Override + public Bundle addAccount(AccountAuthenticatorResponse response, + String accountType, String authTokenType, + String[] requiredFeatures, Bundle options) + throws NetworkErrorException { + Log_OC.i(TAG, "Adding account with type " + accountType + + " and auth token " + authTokenType); + + final Bundle bundle = new Bundle(); + + AccountManager accountManager = AccountManager.get(mContext); + Account[] accounts = accountManager.getAccountsByType(MainApp.getAccountType()); + + if (mContext.getResources().getBoolean(R.bool.multiaccount_support) || accounts.length < 1) { + try { + validateAccountType(accountType); + } catch (AuthenticatorException e) { + Log_OC.e(TAG, "Failed to validate account type " + accountType + ": " + + e.getMessage()); + e.printStackTrace(); + return e.getFailureBundle(); + } + + final Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); + intent.putExtra(KEY_REQUIRED_FEATURES, requiredFeatures); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + intent.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_CREATE); + + setIntentFlags(intent); + + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + + } else { + + // Return an error + bundle.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION); + final String message = String.format(mContext.getString(R.string.auth_unsupported_multiaccount), mContext.getString(R.string.app_name)); + bundle.putString(AccountManager.KEY_ERROR_MESSAGE, message); + + mHandler.post(new Runnable() { + + @Override + public void run() { + Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show(); + } + }); + + } + + return bundle; + } + + /** + * {@inheritDoc} + */ + @Override + public Bundle confirmCredentials(AccountAuthenticatorResponse response, + Account account, Bundle options) throws NetworkErrorException { + try { + validateAccountType(account.type); + } catch (AuthenticatorException e) { + Log_OC.e(TAG, "Failed to validate account type " + account.type + ": " + + e.getMessage()); + e.printStackTrace(); + return e.getFailureBundle(); + } + Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, + response); + intent.putExtra(KEY_ACCOUNT, account); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + + setIntentFlags(intent); + + Bundle resultBundle = new Bundle(); + resultBundle.putParcelable(AccountManager.KEY_INTENT, intent); + return resultBundle; + } + + @Override + public Bundle editProperties(AccountAuthenticatorResponse response, + String accountType) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public Bundle getAuthToken(AccountAuthenticatorResponse response, + Account account, String authTokenType, Bundle options) + throws NetworkErrorException { + /// validate parameters + try { + validateAccountType(account.type); + validateAuthTokenType(authTokenType); + } catch (AuthenticatorException e) { + Log_OC.e(TAG, "Failed to validate account type " + account.type + ": " + + e.getMessage()); + e.printStackTrace(); + return e.getFailureBundle(); + } + + /// check if required token is stored + final AccountManager am = AccountManager.get(mContext); + String accessToken; + if (authTokenType.equals(MainApp.getAuthTokenTypePass())) { + accessToken = am.getPassword(account); + } else { + accessToken = am.peekAuthToken(account, authTokenType); + } + if (accessToken != null) { + final Bundle result = new Bundle(); + result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, MainApp.getAccountType()); + result.putString(AccountManager.KEY_AUTHTOKEN, accessToken); + return result; + } + + /// if not stored, return Intent to access the AuthenticatorActivity and UPDATE the token for the account + final Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + intent.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, account); + intent.putExtra(AuthenticatorActivity.EXTRA_ENFORCED_UPDATE, true); + intent.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_TOKEN); + + + final Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return bundle; + } + + @Override + public String getAuthTokenLabel(String authTokenType) { + return null; + } + + @Override + public Bundle hasFeatures(AccountAuthenticatorResponse response, + Account account, String[] features) throws NetworkErrorException { + final Bundle result = new Bundle(); + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); + return result; + } + + @Override + public Bundle updateCredentials(AccountAuthenticatorResponse response, + Account account, String authTokenType, Bundle options) + throws NetworkErrorException { + final Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, + response); + intent.putExtra(KEY_ACCOUNT, account); + intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + setIntentFlags(intent); + + final Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return bundle; + } + + @Override + public Bundle getAccountRemovalAllowed( + AccountAuthenticatorResponse response, Account account) + throws NetworkErrorException { + return super.getAccountRemovalAllowed(response, account); + } + + private void setIntentFlags(Intent intent) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + intent.addFlags(Intent.FLAG_FROM_BACKGROUND); + } + + private void validateAccountType(String type) + throws UnsupportedAccountTypeException { + if (!type.equals(MainApp.getAccountType())) { + throw new UnsupportedAccountTypeException(); + } + } + + private void validateAuthTokenType(String authTokenType) + throws UnsupportedAuthTokenTypeException { + if (!authTokenType.equals(MainApp.getAuthTokenType()) && + !authTokenType.equals(MainApp.getAuthTokenTypePass()) && + !authTokenType.equals(MainApp.getAuthTokenTypeAccessToken()) && + !authTokenType.equals(MainApp.getAuthTokenTypeRefreshToken()) && + !authTokenType.equals(MainApp.getAuthTokenTypeSamlSessionCookie())) { + throw new UnsupportedAuthTokenTypeException(); + } + } + + public static class AuthenticatorException extends Exception { + private static final long serialVersionUID = 1L; + private Bundle mFailureBundle; + + public AuthenticatorException(int code, String errorMsg) { + mFailureBundle = new Bundle(); + mFailureBundle.putInt(AccountManager.KEY_ERROR_CODE, code); + mFailureBundle + .putString(AccountManager.KEY_ERROR_MESSAGE, errorMsg); + } + + public Bundle getFailureBundle() { + return mFailureBundle; + } + } + + public static class UnsupportedAccountTypeException extends + AuthenticatorException { + private static final long serialVersionUID = 1L; + + public UnsupportedAccountTypeException() { + super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, + "Unsupported account type"); + } + } + + public static class UnsupportedAuthTokenTypeException extends + AuthenticatorException { + private static final long serialVersionUID = 1L; + + public UnsupportedAuthTokenTypeException() { + super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, + "Unsupported auth token type"); + } + } + + public static class UnsupportedFeaturesException extends + AuthenticatorException { + public static final long serialVersionUID = 1L; + + public UnsupportedFeaturesException() { + super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, + "Unsupported features"); + } + } + + public static class AccessDeniedException extends AuthenticatorException { + public AccessDeniedException(int code, String errorMsg) { + super(AccountManager.ERROR_CODE_INVALID_RESPONSE, "Access Denied"); + } + + private static final long serialVersionUID = 1L; + + } +} diff --git a/src/com/owncloud/android/authentication/AccountAuthenticatorActivity.java b/src/com/owncloud/android/authentication/AccountAuthenticatorActivity.java new file mode 100644 index 00000000..62c8825f --- /dev/null +++ b/src/com/owncloud/android/authentication/AccountAuthenticatorActivity.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.owncloud.android.authentication; + +import android.accounts.AccountAuthenticatorResponse; +import android.accounts.AccountManager; +import android.os.Bundle; + +import com.actionbarsherlock.app.SherlockFragmentActivity; + + +/* + * Base class for implementing an Activity that is used to help implement an AbstractAccountAuthenticator. + * If the AbstractAccountAuthenticator needs to use an activity to handle the request then it can have the activity extend + * AccountAuthenticatorActivity. The AbstractAccountAuthenticator passes in the response to the intent using the following: + * intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + * + * The activity then sets the result that is to be handed to the response via setAccountAuthenticatorResult(android.os.Bundle). + * This result will be sent as the result of the request when the activity finishes. If this is never set or if it is set to null + * then error AccountManager.ERROR_CODE_CANCELED will be called on the response. + */ + +public class AccountAuthenticatorActivity extends SherlockFragmentActivity { + + private AccountAuthenticatorResponse mAccountAuthenticatorResponse = null; + private Bundle mResultBundle = null; + + + /** + * Set the result that is to be sent as the result of the request that caused this Activity to be launched. + * If result is null or this method is never called then the request will be canceled. + * + * @param result this is returned as the result of the AbstractAccountAuthenticator request + */ + public final void setAccountAuthenticatorResult(Bundle result) { + mResultBundle = result; + } + + /** + * Retreives the AccountAuthenticatorResponse from either the intent of the icicle, if the + * icicle is non-zero. + * @param icicle the save instance data of this Activity, may be null + */ + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mAccountAuthenticatorResponse = + getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE); + + if (mAccountAuthenticatorResponse != null) { + mAccountAuthenticatorResponse.onRequestContinued(); + } + } + + /** + * Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present. + */ + public void finish() { + if (mAccountAuthenticatorResponse != null) { + // send the result bundle back if set, otherwise send an error. + if (mResultBundle != null) { + mAccountAuthenticatorResponse.onResult(mResultBundle); + } else { + mAccountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, + "canceled"); + } + mAccountAuthenticatorResponse = null; + } + super.finish(); + } +} diff --git a/src/com/owncloud/android/authentication/AccountAuthenticatorService.java b/src/com/owncloud/android/authentication/AccountAuthenticatorService.java new file mode 100644 index 00000000..4c91f6e4 --- /dev/null +++ b/src/com/owncloud/android/authentication/AccountAuthenticatorService.java @@ -0,0 +1,41 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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.authentication; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +public class AccountAuthenticatorService extends Service { + + private AccountAuthenticator mAuthenticator; + //static final public String ACCOUNT_TYPE = "owncloud"; + + @Override + public void onCreate() { + super.onCreate(); + mAuthenticator = new AccountAuthenticator(this); + } + + @Override + public IBinder onBind(Intent intent) { + return mAuthenticator.getIBinder(); + } + +} diff --git a/src/com/owncloud/android/authentication/AccountUtils.java b/src/com/owncloud/android/authentication/AccountUtils.java new file mode 100644 index 00000000..3b79c39b --- /dev/null +++ b/src/com/owncloud/android/authentication/AccountUtils.java @@ -0,0 +1,220 @@ +/* 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.authentication; + +import com.owncloud.android.MainApp; +import com.owncloud.android.utils.OwnCloudVersion; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountsException; +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +public class AccountUtils { + public static final String WEBDAV_PATH_1_2 = "/webdav/owncloud.php"; + public static final String WEBDAV_PATH_2_0 = "/files/webdav.php"; + public static final String WEBDAV_PATH_4_0 = "/remote.php/webdav"; + private static final String ODAV_PATH = "/remote.php/odav"; + private static final String SAML_SSO_PATH = "/remote.php/webdav"; + public static final String CARDDAV_PATH_2_0 = "/apps/contacts/carddav.php"; + public static final String CARDDAV_PATH_4_0 = "/remote/carddav.php"; + public static final String STATUS_PATH = "/status.php"; + + /** + * Can be used to get the currently selected ownCloud {@link Account} in the + * application preferences. + * + * @param context The current application {@link Context} + * @return The ownCloud {@link Account} currently saved in preferences, or the first + * {@link Account} available, if valid (still registered in the system as ownCloud + * account). If none is available and valid, returns null. + */ + public static Account getCurrentOwnCloudAccount(Context context) { + Account[] ocAccounts = AccountManager.get(context).getAccountsByType( + MainApp.getAccountType()); + Account defaultAccount = null; + + SharedPreferences appPreferences = PreferenceManager + .getDefaultSharedPreferences(context); + String accountName = appPreferences + .getString("select_oc_account", null); + + // account validation: the saved account MUST be in the list of ownCloud Accounts known by the AccountManager + if (accountName != null) { + for (Account account : ocAccounts) { + if (account.name.equals(accountName)) { + defaultAccount = account; + break; + } + } + } + + if (defaultAccount == null && ocAccounts.length != 0) { + // take first account as fallback + defaultAccount = ocAccounts[0]; + } + + return defaultAccount; + } + + + public static boolean exists(Account account, Context context) { + Account[] ocAccounts = AccountManager.get(context).getAccountsByType( + MainApp.getAccountType()); + + if (account != null && account.name != null) { + for (Account ac : ocAccounts) { + if (ac.name.equals(account.name)) { + return true; + } + } + } + return false; + } + + + /** + * Checks, whether or not there are any ownCloud accounts setup. + * + * @return true, if there is at least one account. + */ + public static boolean accountsAreSetup(Context context) { + AccountManager accMan = AccountManager.get(context); + Account[] accounts = accMan + .getAccountsByType(MainApp.getAccountType()); + return accounts.length > 0; + } + + + public static boolean setCurrentOwnCloudAccount(Context context, String accountName) { + boolean result = false; + if (accountName != null) { + Account[] ocAccounts = AccountManager.get(context).getAccountsByType( + MainApp.getAccountType()); + boolean found = false; + for (Account account : ocAccounts) { + found = (account.name.equals(accountName)); + if (found) { + SharedPreferences.Editor appPrefs = PreferenceManager + .getDefaultSharedPreferences(context).edit(); + appPrefs.putString("select_oc_account", accountName); + + appPrefs.commit(); + result = true; + break; + } + } + } + return result; + } + + /** + * + * @param version version of owncloud + * @return webdav path for given OC version, null if OC version unknown + */ + public static String getWebdavPath(OwnCloudVersion version, boolean supportsOAuth, boolean supportsSamlSso) { + if (version != null) { + if (supportsOAuth) { + return ODAV_PATH; + } + if (supportsSamlSso) { + return SAML_SSO_PATH; + } + if (version.compareTo(OwnCloudVersion.owncloud_v4) >= 0) + return WEBDAV_PATH_4_0; + if (version.compareTo(OwnCloudVersion.owncloud_v3) >= 0 + || version.compareTo(OwnCloudVersion.owncloud_v2) >= 0) + return WEBDAV_PATH_2_0; + if (version.compareTo(OwnCloudVersion.owncloud_v1) >= 0) + return WEBDAV_PATH_1_2; + } + return null; + } + + /** + * Returns the proper URL path to access the WebDAV interface of an ownCloud server, + * according to its version and the authorization method used. + * + * @param version Version of ownCloud server. + * @param authTokenType Authorization token type, matching some of the AUTH_TOKEN_TYPE_* constants in {@link AccountAuthenticator}. + * @return WebDAV path for given OC version and authorization method, null if OC version is unknown. + */ + public static String getWebdavPath(OwnCloudVersion version, String authTokenType) { + if (version != null) { + if (MainApp.getAuthTokenTypeAccessToken().equals(authTokenType)) { + return ODAV_PATH; + } + if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(authTokenType)) { + return SAML_SSO_PATH; + } + if (version.compareTo(OwnCloudVersion.owncloud_v4) >= 0) + return WEBDAV_PATH_4_0; + if (version.compareTo(OwnCloudVersion.owncloud_v3) >= 0 + || version.compareTo(OwnCloudVersion.owncloud_v2) >= 0) + return WEBDAV_PATH_2_0; + if (version.compareTo(OwnCloudVersion.owncloud_v1) >= 0) + return WEBDAV_PATH_1_2; + } + return null; + } + + /** + * Constructs full url to host and webdav resource basing on host version + * @param context + * @param account + * @return url or null on failure + * @throws AccountNotFoundException When 'account' is unknown for the AccountManager + */ + public static String constructFullURLForAccount(Context context, Account account) throws AccountNotFoundException { + AccountManager ama = AccountManager.get(context); + String baseurl = ama.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL); + String strver = ama.getUserData(account, AccountAuthenticator.KEY_OC_VERSION); + boolean supportsOAuth = (ama.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null); + boolean supportsSamlSso = (ama.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_SAML_WEB_SSO) != null); + OwnCloudVersion ver = new OwnCloudVersion(strver); + String webdavpath = getWebdavPath(ver, supportsOAuth, supportsSamlSso); + + if (baseurl == null || webdavpath == null) + throw new AccountNotFoundException(account, "Account not found", null); + + return baseurl + webdavpath; + } + + + public static class AccountNotFoundException extends AccountsException { + + /** Generated - should be refreshed every time the class changes!! */ + private static final long serialVersionUID = -9013287181793186830L; + + private Account mFailedAccount; + + public AccountNotFoundException(Account failedAccount, String message, Throwable cause) { + super(message, cause); + mFailedAccount = failedAccount; + } + + public Account getFailedAccount() { + return mFailedAccount; + } + } + +} diff --git a/src/com/owncloud/android/authentication/AuthenticatorActivity.java b/src/com/owncloud/android/authentication/AuthenticatorActivity.java new file mode 100644 index 00000000..69872d73 --- /dev/null +++ b/src/com/owncloud/android/authentication/AuthenticatorActivity.java @@ -0,0 +1,1672 @@ +/* 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.authentication; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.ContentResolver; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnFocusChangeListener; +import android.view.View.OnTouchListener; +import android.view.Window; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + +import com.actionbarsherlock.app.SherlockDialogFragment; +import com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; +import com.owncloud.android.R; +import com.owncloud.android.authentication.SsoWebViewClient.SsoWebViewClientListener; +import com.owncloud.android.network.OwnCloudClientUtils; +import com.owncloud.android.operations.ExistenceCheckOperation; +import com.owncloud.android.operations.OAuth2GetAccessToken; +import com.owncloud.android.operations.OnRemoteOperationListener; +import com.owncloud.android.operations.OwnCloudServerCheckOperation; +import com.owncloud.android.operations.RemoteOperation; +import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.ui.CustomButton; +import com.owncloud.android.ui.dialog.SamlWebViewDialog; +import com.owncloud.android.ui.dialog.SslValidatorDialog; +import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener; +import com.owncloud.android.utils.OwnCloudVersion; + + +import eu.alefzero.webdav.WebdavClient; + +/** + * This Activity is used to add an ownCloud account to the App + * + * @author Bartek Przybylski + * @author David A. Velasco + */ +public class AuthenticatorActivity extends AccountAuthenticatorActivity +implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeListener, OnEditorActionListener, SsoWebViewClientListener{ + + private static final String TAG = AuthenticatorActivity.class.getSimpleName(); + + public static final String EXTRA_ACCOUNT = "ACCOUNT"; + public static final String EXTRA_USER_NAME = "USER_NAME"; + public static final String EXTRA_HOST_NAME = "HOST_NAME"; + public static final String EXTRA_ACTION = "ACTION"; + public static final String EXTRA_ENFORCED_UPDATE = "ENFORCE_UPDATE"; + + private static final String KEY_AUTH_MESSAGE_VISIBILITY = "AUTH_MESSAGE_VISIBILITY"; + private static final String KEY_AUTH_MESSAGE_TEXT = "AUTH_MESSAGE_TEXT"; + private static final String KEY_HOST_URL_TEXT = "HOST_URL_TEXT"; + private static final String KEY_OC_VERSION = "OC_VERSION"; + private static final String KEY_ACCOUNT = "ACCOUNT"; + private static final String KEY_SERVER_VALID = "SERVER_VALID"; + private static final String KEY_SERVER_CHECKED = "SERVER_CHECKED"; + private static final String KEY_SERVER_CHECK_IN_PROGRESS = "SERVER_CHECK_IN_PROGRESS"; + private static final String KEY_SERVER_STATUS_TEXT = "SERVER_STATUS_TEXT"; + private static final String KEY_SERVER_STATUS_ICON = "SERVER_STATUS_ICON"; + private static final String KEY_IS_SSL_CONN = "IS_SSL_CONN"; + private static final String KEY_PASSWORD_VISIBLE = "PASSWORD_VISIBLE"; + private static final String KEY_AUTH_STATUS_TEXT = "AUTH_STATUS_TEXT"; + private static final String KEY_AUTH_STATUS_ICON = "AUTH_STATUS_ICON"; + private static final String KEY_REFRESH_BUTTON_ENABLED = "KEY_REFRESH_BUTTON_ENABLED"; + + private static final String KEY_OC_USERNAME_EQUALS = "oc_username="; + + private static final String AUTH_ON = "on"; + private static final String AUTH_OFF = "off"; + private static final String AUTH_OPTIONAL = "optional"; + + private static final int DIALOG_LOGIN_PROGRESS = 0; + private static final int DIALOG_SSL_VALIDATOR = 1; + private static final int DIALOG_CERT_NOT_SAVED = 2; + private static final int DIALOG_OAUTH2_LOGIN_PROGRESS = 3; + + public static final byte ACTION_CREATE = 0; + public static final byte ACTION_UPDATE_TOKEN = 1; + + private static final String TAG_SAML_DIALOG = "samlWebViewDialog"; + + private String mHostBaseUrl; + private OwnCloudVersion mDiscoveredVersion; + + private String mAuthMessageText; + private int mAuthMessageVisibility, mServerStatusText, mServerStatusIcon; + private boolean mServerIsChecked, mServerIsValid, mIsSslConn; + private int mAuthStatusText, mAuthStatusIcon; + private TextView mAuthStatusLayout; + + private final Handler mHandler = new Handler(); + private Thread mOperationThread; + private OwnCloudServerCheckOperation mOcServerChkOperation; + private ExistenceCheckOperation mAuthCheckOperation; + private RemoteOperationResult mLastSslUntrustedServerResult; + + private Uri mNewCapturedUriFromOAuth2Redirection; + + private AccountManager mAccountMgr; + private boolean mJustCreated; + private byte mAction; + private Account mAccount; + + private TextView mAuthMessage; + + private EditText mHostUrlInput; + private boolean mHostUrlInputEnabled; + private View mRefreshButton; + + private String mAuthTokenType; + + private EditText mUsernameInput; + private EditText mPasswordInput; + + private CheckBox mOAuth2Check; + + private TextView mOAuthAuthEndpointText; + private TextView mOAuthTokenEndpointText; + + private SamlWebViewDialog mSamlDialog; + + private View mOkButton; + + private String mAuthToken; + + private boolean mResumed; // Control if activity is resumed + + + /** + * {@inheritDoc} + * + * IMPORTANT ENTRY POINT 1: activity is shown to the user + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().requestFeature(Window.FEATURE_NO_TITLE); + + /// set view and get references to view elements + setContentView(R.layout.account_setup); + mAuthMessage = (TextView) findViewById(R.id.auth_message); + mHostUrlInput = (EditText) findViewById(R.id.hostUrlInput); + mHostUrlInput.setText(getString(R.string.server_url)); // valid although R.string.server_url is an empty string + mUsernameInput = (EditText) findViewById(R.id.account_username); + mPasswordInput = (EditText) findViewById(R.id.account_password); + mOAuthAuthEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_1); + mOAuthTokenEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_2); + mOAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check); + mOkButton = (CustomButton) findViewById(R.id.buttonOK); + mAuthStatusLayout = (TextView) findViewById(R.id.auth_status_text); + + /// set Host Url Input Enabled + mHostUrlInputEnabled = getResources().getBoolean(R.bool.show_server_url_input); + + + /// complete label for 'register account' button + Button b = (Button) findViewById(R.id.account_register); + if (b != null) { + b.setText(String.format(getString(R.string.auth_register), getString(R.string.app_name))); + } + +// /// complete background of 'OK' button +// boolean customButtons = getResources().getBoolean(R.bool.custom_buttons); +// if (customButtons) +// mOkButton.setBackgroundResource(R.drawable.btn_default); + + /// initialization + mAccountMgr = AccountManager.get(this); + mNewCapturedUriFromOAuth2Redirection = null; + mAction = getIntent().getByteExtra(EXTRA_ACTION, ACTION_CREATE); + mAccount = null; + mHostBaseUrl = ""; + boolean refreshButtonEnabled = false; + + // URL input configuration applied + if (!mHostUrlInputEnabled) + { + findViewById(R.id.hostUrlFrame).setVisibility(View.GONE); + mRefreshButton = findViewById(R.id.centeredRefreshButton); + + } else { + mRefreshButton = findViewById(R.id.embeddedRefreshButton); + } + + if (savedInstanceState == null) { + mResumed = false; + /// connection state and info + mAuthMessageVisibility = View.GONE; + mServerStatusText = mServerStatusIcon = 0; + mServerIsValid = false; + mServerIsChecked = false; + mIsSslConn = false; + mAuthStatusText = mAuthStatusIcon = 0; + + /// retrieve extras from intent + mAccount = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT); + if (mAccount != null) { + String ocVersion = mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION); + if (ocVersion != null) { + mDiscoveredVersion = new OwnCloudVersion(ocVersion); + } + mHostBaseUrl = normalizeUrl(mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL)); + mHostUrlInput.setText(mHostBaseUrl); + String userName = mAccount.name.substring(0, mAccount.name.lastIndexOf('@')); + mUsernameInput.setText(userName); + } + initAuthorizationMethod(); // checks intent and setup.xml to determine mCurrentAuthorizationMethod + mJustCreated = true; + + if (mAction == ACTION_UPDATE_TOKEN || !mHostUrlInputEnabled) { + checkOcServer(); + } + + } else { + mResumed = true; + /// connection state and info + mAuthMessageVisibility = savedInstanceState.getInt(KEY_AUTH_MESSAGE_VISIBILITY); + mAuthMessageText = savedInstanceState.getString(KEY_AUTH_MESSAGE_TEXT); + mServerIsValid = savedInstanceState.getBoolean(KEY_SERVER_VALID); + mServerIsChecked = savedInstanceState.getBoolean(KEY_SERVER_CHECKED); + mServerStatusText = savedInstanceState.getInt(KEY_SERVER_STATUS_TEXT); + mServerStatusIcon = savedInstanceState.getInt(KEY_SERVER_STATUS_ICON); + mIsSslConn = savedInstanceState.getBoolean(KEY_IS_SSL_CONN); + mAuthStatusText = savedInstanceState.getInt(KEY_AUTH_STATUS_TEXT); + mAuthStatusIcon = savedInstanceState.getInt(KEY_AUTH_STATUS_ICON); + if (savedInstanceState.getBoolean(KEY_PASSWORD_VISIBLE, false)) { + showPassword(); + } + + /// server data + String ocVersion = savedInstanceState.getString(KEY_OC_VERSION); + if (ocVersion != null) { + mDiscoveredVersion = new OwnCloudVersion(ocVersion); + } + mHostBaseUrl = savedInstanceState.getString(KEY_HOST_URL_TEXT); + + // account data, if updating + mAccount = savedInstanceState.getParcelable(KEY_ACCOUNT); + mAuthTokenType = savedInstanceState.getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE); + if (mAuthTokenType == null) { + mAuthTokenType = MainApp.getAuthTokenTypePass(); + + } + + // check if server check was interrupted by a configuration change + if (savedInstanceState.getBoolean(KEY_SERVER_CHECK_IN_PROGRESS, false)) { + checkOcServer(); + } + + // refresh button enabled + refreshButtonEnabled = savedInstanceState.getBoolean(KEY_REFRESH_BUTTON_ENABLED); + + + } + + if (mAuthMessageVisibility== View.VISIBLE) { + showAuthMessage(mAuthMessageText); + } + else { + hideAuthMessage(); + } + adaptViewAccordingToAuthenticationMethod(); + showServerStatus(); + showAuthStatus(); + + if (mAction == ACTION_UPDATE_TOKEN) { + /// lock things that should not change + mHostUrlInput.setEnabled(false); + mHostUrlInput.setFocusable(false); + mUsernameInput.setEnabled(false); + mUsernameInput.setFocusable(false); + mOAuth2Check.setVisibility(View.GONE); + } + + //if (mServerIsChecked && !mServerIsValid && mRefreshButtonEnabled) showRefreshButton(); + if (mServerIsChecked && !mServerIsValid && refreshButtonEnabled) showRefreshButton(); + mOkButton.setEnabled(mServerIsValid); // state not automatically recovered in configuration changes + + if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType) || + !AUTH_OPTIONAL.equals(getString(R.string.auth_method_oauth2))) { + mOAuth2Check.setVisibility(View.GONE); + } + + mPasswordInput.setText(""); // clean password to avoid social hacking (disadvantage: password in removed if the device is turned aside) + + /// bind view elements to listeners and other friends + mHostUrlInput.setOnFocusChangeListener(this); + mHostUrlInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); + mHostUrlInput.setOnEditorActionListener(this); + mHostUrlInput.addTextChangedListener(new TextWatcher() { + + @Override + public void afterTextChanged(Editable s) { + if (!mHostBaseUrl.equals(normalizeUrl(mHostUrlInput.getText().toString()))) { + mOkButton.setEnabled(false); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (!mResumed) { + mAuthStatusIcon = 0; + mAuthStatusText = 0; + showAuthStatus(); + } + mResumed = false; + } + }); + + mPasswordInput.setOnFocusChangeListener(this); + mPasswordInput.setImeOptions(EditorInfo.IME_ACTION_DONE); + mPasswordInput.setOnEditorActionListener(this); + mPasswordInput.setOnTouchListener(new RightDrawableOnTouchListener() { + @Override + public boolean onDrawableTouch(final MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + AuthenticatorActivity.this.onViewPasswordClick(); + } + return true; + } + }); + + findViewById(R.id.scroll).setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType) && + mHostUrlInput.hasFocus()) { + checkOcServer(); + } + } + return false; + } + }); + } + + + + private void initAuthorizationMethod() { + boolean oAuthRequired = false; + boolean samlWebSsoRequired = false; + + mAuthTokenType = getIntent().getExtras().getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE); + mAccount = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT); + + // TODO could be a good moment to validate the received token type, if not null + + if (mAuthTokenType == null) { + if (mAccount != null) { + /// same authentication method than the one used to create the account to update + oAuthRequired = (mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null); + samlWebSsoRequired = (mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_SAML_WEB_SSO) != null); + + } else { + /// use the one set in setup.xml + oAuthRequired = AUTH_ON.equals(getString(R.string.auth_method_oauth2)); + samlWebSsoRequired = AUTH_ON.equals(getString(R.string.auth_method_saml_web_sso)); + } + if (oAuthRequired) { + mAuthTokenType = MainApp.getAuthTokenTypeAccessToken(); + } else if (samlWebSsoRequired) { + mAuthTokenType = MainApp.getAuthTokenTypeSamlSessionCookie(); + } else { + mAuthTokenType = MainApp.getAuthTokenTypePass(); + } + } + + if (mAccount != null) { + String userName = mAccount.name.substring(0, mAccount.name.lastIndexOf('@')); + mUsernameInput.setText(userName); + } + + mOAuth2Check.setChecked(MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType)); + + } + + /** + * Saves relevant state before {@link #onPause()} + * + * Do NOT save {@link #mNewCapturedUriFromOAuth2Redirection}; it keeps a temporal flag, intended to defer the + * processing of the redirection caught in {@link #onNewIntent(Intent)} until {@link #onResume()} + * + * See {@link #loadSavedInstanceState(Bundle)} + */ + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + /// connection state and info + outState.putInt(KEY_AUTH_MESSAGE_VISIBILITY, mAuthMessage.getVisibility()); + outState.putString(KEY_AUTH_MESSAGE_TEXT, mAuthMessage.getText().toString()); + outState.putInt(KEY_SERVER_STATUS_TEXT, mServerStatusText); + outState.putInt(KEY_SERVER_STATUS_ICON, mServerStatusIcon); + outState.putBoolean(KEY_SERVER_VALID, mServerIsValid); + outState.putBoolean(KEY_SERVER_CHECKED, mServerIsChecked); + outState.putBoolean(KEY_SERVER_CHECK_IN_PROGRESS, (!mServerIsValid && mOcServerChkOperation != null)); + outState.putBoolean(KEY_IS_SSL_CONN, mIsSslConn); + outState.putBoolean(KEY_PASSWORD_VISIBLE, isPasswordVisible()); + outState.putInt(KEY_AUTH_STATUS_ICON, mAuthStatusIcon); + outState.putInt(KEY_AUTH_STATUS_TEXT, mAuthStatusText); + + /// server data + if (mDiscoveredVersion != null) { + outState.putString(KEY_OC_VERSION, mDiscoveredVersion.toString()); + } + outState.putString(KEY_HOST_URL_TEXT, mHostBaseUrl); + + /// account data, if updating + if (mAccount != null) { + outState.putParcelable(KEY_ACCOUNT, mAccount); + } + outState.putString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE, mAuthTokenType); + + // refresh button enabled + outState.putBoolean(KEY_REFRESH_BUTTON_ENABLED, (mRefreshButton.getVisibility() == View.VISIBLE)); + + + } + + + /** + * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION request + * is caught here. + * + * To make this possible, this activity needs to be qualified with android:launchMode = "singleTask" in the + * AndroidManifest.xml file. + */ + @Override + protected void onNewIntent (Intent intent) { + Log_OC.d(TAG, "onNewIntent()"); + Uri data = intent.getData(); + if (data != null && data.toString().startsWith(getString(R.string.oauth2_redirect_uri))) { + mNewCapturedUriFromOAuth2Redirection = data; + } + } + + + /** + * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION, and + * deferred in {@link #onNewIntent(Intent)}, is processed here. + */ + @Override + protected void onResume() { + super.onResume(); + if (mAction == ACTION_UPDATE_TOKEN && mJustCreated && getIntent().getBooleanExtra(EXTRA_ENFORCED_UPDATE, false)) { + if (MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType)) { + //Toast.makeText(this, R.string.auth_expired_oauth_token_toast, Toast.LENGTH_LONG).show(); + showAuthMessage(getString(R.string.auth_expired_oauth_token_toast)); + } else if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType)) { + //Toast.makeText(this, R.string.auth_expired_saml_sso_token_toast, Toast.LENGTH_LONG).show(); + showAuthMessage(getString(R.string.auth_expired_saml_sso_token_toast)); + } else { + //Toast.makeText(this, R.string.auth_expired_basic_auth_toast, Toast.LENGTH_LONG).show(); + showAuthMessage(getString(R.string.auth_expired_basic_auth_toast)); + } + } + + if (mNewCapturedUriFromOAuth2Redirection != null) { + getOAuth2AccessTokenFromCapturedRedirection(); + } + + mJustCreated = false; + + } + + + /** + * Parses the redirection with the response to the GET AUTHORIZATION request to the + * oAuth server and requests for the access token (GET ACCESS TOKEN) + */ + private void getOAuth2AccessTokenFromCapturedRedirection() { + /// Parse data from OAuth redirection + String queryParameters = mNewCapturedUriFromOAuth2Redirection.getQuery(); + mNewCapturedUriFromOAuth2Redirection = null; + + /// Showing the dialog with instructions for the user. + showDialog(DIALOG_OAUTH2_LOGIN_PROGRESS); + + /// GET ACCESS TOKEN to the oAuth server + RemoteOperation operation = new OAuth2GetAccessToken( getString(R.string.oauth2_client_id), + getString(R.string.oauth2_redirect_uri), + getString(R.string.oauth2_grant_type), + queryParameters); + //WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(getString(R.string.oauth2_url_endpoint_access)), getApplicationContext()); + WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mOAuthTokenEndpointText.getText().toString().trim()), getApplicationContext(), true); + operation.execute(client, this, mHandler); + } + + + + /** + * Handles the change of focus on the text inputs for the server URL and the password + */ + public void onFocusChange(View view, boolean hasFocus) { + if (view.getId() == R.id.hostUrlInput) { + if (!hasFocus) { + onUrlInputFocusLost((TextView) view); + } + else { + hideRefreshButton(); + } + + } else if (view.getId() == R.id.account_password) { + onPasswordFocusChanged((TextView) view, hasFocus); + } + } + + + /** + * Handles changes in focus on the text input for the server URL. + * + * IMPORTANT ENTRY POINT 2: When (!hasFocus), user wrote the server URL and changed to + * other field. The operation to check the existence of the server in the entered URL is + * started. + * + * When hasFocus: user 'comes back' to write again the server URL. + * + * @param hostInput TextView with the URL input field receiving the change of focus. + */ + private void onUrlInputFocusLost(TextView hostInput) { + if (!mHostBaseUrl.equals(normalizeUrl(mHostUrlInput.getText().toString()))) { + checkOcServer(); + } else { + mOkButton.setEnabled(mServerIsValid); + if (!mServerIsValid) { + showRefreshButton(); + } + } + } + + + private void checkOcServer() { + String uri = trimUrlWebdav(mHostUrlInput.getText().toString().trim()); + + if (!mHostUrlInputEnabled){ + uri = getString(R.string.server_url); + } + + mServerIsValid = false; + mServerIsChecked = false; + mOkButton.setEnabled(false); + mDiscoveredVersion = null; + hideRefreshButton(); + if (uri.length() != 0) { + mServerStatusText = R.string.auth_testing_connection; + mServerStatusIcon = R.drawable.progress_small; + showServerStatus(); + mOcServerChkOperation = new OwnCloudServerCheckOperation(uri, this); + WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(uri), this, true); + mOperationThread = mOcServerChkOperation.execute(client, this, mHandler); + } else { + mServerStatusText = 0; + mServerStatusIcon = 0; + showServerStatus(); + } + } + + + /** + * Handles changes in focus on the text input for the password (basic authorization). + * + * When (hasFocus), the button to toggle password visibility is shown. + * + * When (!hasFocus), the button is made invisible and the password is hidden. + * + * @param passwordInput TextView with the password input field receiving the change of focus. + * @param hasFocus 'True' if focus is received, 'false' if is lost + */ + private void onPasswordFocusChanged(TextView passwordInput, boolean hasFocus) { + if (hasFocus) { + showViewPasswordButton(); + } else { + hidePassword(); + hidePasswordButton(); + } + } + + + private void showViewPasswordButton() { + //int drawable = android.R.drawable.ic_menu_view; + int drawable = R.drawable.ic_view; + if (isPasswordVisible()) { + //drawable = android.R.drawable.ic_secure; + drawable = R.drawable.ic_hide; + } + mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, drawable, 0); + } + + private boolean isPasswordVisible() { + return ((mPasswordInput.getInputType() & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + } + + private void hidePasswordButton() { + mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + } + + private void showPassword() { + mPasswordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + showViewPasswordButton(); + } + + private void hidePassword() { + mPasswordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + showViewPasswordButton(); + } + + + /** + * Cancels the authenticator activity + * + * IMPORTANT ENTRY POINT 3: Never underestimate the importance of cancellation + * + * This method is bound in the layout/acceoun_setup.xml resource file. + * + * @param view Cancel button + */ + public void onCancelClick(View view) { + setResult(RESULT_CANCELED); // TODO review how is this related to AccountAuthenticator (debugging) + finish(); + } + + + + /** + * Checks the credentials of the user in the root of the ownCloud server + * before creating a new local account. + * + * For basic authorization, a check of existence of the root folder is + * performed. + * + * For OAuth, starts the flow to get an access token; the credentials test + * is postponed until it is available. + * + * IMPORTANT ENTRY POINT 4 + * + * @param view OK button + */ + public void onOkClick(View view) { + // this check should be unnecessary + if (mDiscoveredVersion == null || !mDiscoveredVersion.isVersionValid() || mHostBaseUrl == null || mHostBaseUrl.length() == 0) { + mServerStatusIcon = R.drawable.common_error; + mServerStatusText = R.string.auth_wtf_reenter_URL; + showServerStatus(); + mOkButton.setEnabled(false); + Log_OC.wtf(TAG, "The user was allowed to click 'connect' to an unchecked server!!"); + return; + } + + if (MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType)) { + startOauthorization(); + } else if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType)) { + startSamlBasedFederatedSingleSignOnAuthorization(); + } else { + checkBasicAuthorization(); + } + } + + + /** + * Tests the credentials entered by the user performing a check of existence on + * the root folder of the ownCloud server. + */ + private void checkBasicAuthorization() { + /// get the path to the root folder through WebDAV from the version server + String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, mAuthTokenType); + + /// get basic credentials entered by user + String username = mUsernameInput.getText().toString(); + String password = mPasswordInput.getText().toString(); + + /// be gentle with the user + showDialog(DIALOG_LOGIN_PROGRESS); + + /// test credentials accessing the root folder + mAuthCheckOperation = new ExistenceCheckOperation("", this, false); + WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, true); + client.setBasicCredentials(username, password); + mOperationThread = mAuthCheckOperation.execute(client, this, mHandler); + } + + + /** + * Starts the OAuth 'grant type' flow to get an access token, with + * a GET AUTHORIZATION request to the BUILT-IN authorization server. + */ + private void startOauthorization() { + // be gentle with the user + mAuthStatusIcon = R.drawable.progress_small; + mAuthStatusText = R.string.oauth_login_connection; + showAuthStatus(); + + + // GET AUTHORIZATION request + //Uri uri = Uri.parse(getString(R.string.oauth2_url_endpoint_auth)); + Uri uri = Uri.parse(mOAuthAuthEndpointText.getText().toString().trim()); + Uri.Builder uriBuilder = uri.buildUpon(); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_RESPONSE_TYPE, getString(R.string.oauth2_response_type)); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_REDIRECT_URI, getString(R.string.oauth2_redirect_uri)); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_CLIENT_ID, getString(R.string.oauth2_client_id)); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_SCOPE, getString(R.string.oauth2_scope)); + //uriBuilder.appendQueryParameter(OAuth2Constants.KEY_STATE, whateverwewant); + uri = uriBuilder.build(); + Log_OC.d(TAG, "Starting browser to view " + uri.toString()); + Intent i = new Intent(Intent.ACTION_VIEW, uri); + startActivity(i); + } + + + /** + * Starts the Web Single Sign On flow to get access to the root folder + * in the server. + */ + private void startSamlBasedFederatedSingleSignOnAuthorization() { + // be gentle with the user + mAuthStatusIcon = R.drawable.progress_small; + mAuthStatusText = R.string.auth_connecting_auth_server; + showAuthStatus(); + showDialog(DIALOG_LOGIN_PROGRESS); + + /// get the path to the root folder through WebDAV from the version server + String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, mAuthTokenType); + + /// test credentials accessing the root folder + mAuthCheckOperation = new ExistenceCheckOperation("", this, false); + WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, false); + mOperationThread = mAuthCheckOperation.execute(client, this, mHandler); + + } + + /** + * Callback method invoked when a RemoteOperation executed by this Activity finishes. + * + * Dispatches the operation flow to the right method. + */ + @Override + public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { + + if (operation instanceof OwnCloudServerCheckOperation) { + onOcServerCheckFinish((OwnCloudServerCheckOperation) operation, result); + + } else if (operation instanceof OAuth2GetAccessToken) { + onGetOAuthAccessTokenFinish((OAuth2GetAccessToken)operation, result); + + } else if (operation instanceof ExistenceCheckOperation) { + if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType)) { + onSamlBasedFederatedSingleSignOnAuthorizationStart(operation, result); + + } else { + onAuthorizationCheckFinish((ExistenceCheckOperation)operation, result); + } + } + } + + + private void onSamlBasedFederatedSingleSignOnAuthorizationStart(RemoteOperation operation, RemoteOperationResult result) { + try { + dismissDialog(DIALOG_LOGIN_PROGRESS); + } catch (IllegalArgumentException e) { + // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens + } + + //if (result.isTemporalRedirection() && result.isIdPRedirection()) { + if (result.isIdPRedirection()) { + String url = result.getRedirectedLocation(); + String targetUrl = mHostBaseUrl + AccountUtils.getWebdavPath(mDiscoveredVersion, mAuthTokenType); + + // Show dialog + mSamlDialog = SamlWebViewDialog.newInstance(url, targetUrl); + mSamlDialog.show(getSupportFragmentManager(), TAG_SAML_DIALOG); + + mAuthStatusIcon = 0; + mAuthStatusText = 0; + + } else { + mAuthStatusIcon = R.drawable.common_error; + mAuthStatusText = R.string.auth_unsupported_auth_method; + + } + showAuthStatus(); + } + + + /** + * Processes the result of the server check performed when the user finishes the enter of the + * server URL. + * + * @param operation Server check performed. + * @param result Result of the check. + */ + private void onOcServerCheckFinish(OwnCloudServerCheckOperation operation, RemoteOperationResult result) { + if (operation.equals(mOcServerChkOperation)) { + /// save result state + mServerIsChecked = true; + mServerIsValid = result.isSuccess(); + mIsSslConn = (result.getCode() == ResultCode.OK_SSL); + mOcServerChkOperation = null; + + /// update status icon and text + if (mServerIsValid) { + hideRefreshButton(); + } else { + showRefreshButton(); + } + updateServerStatusIconAndText(result); + showServerStatus(); + + /// very special case (TODO: move to a common place for all the remote operations) + if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) { + mLastSslUntrustedServerResult = result; + showDialog(DIALOG_SSL_VALIDATOR); + } + + /// retrieve discovered version and normalize server URL + mDiscoveredVersion = operation.getDiscoveredVersion(); + mHostBaseUrl = normalizeUrl(mHostUrlInput.getText().toString()); + + /// allow or not the user try to access the server + mOkButton.setEnabled(mServerIsValid); + + } // else nothing ; only the last check operation is considered; + // multiple can be triggered if the user amends a URL before a previous check can be triggered + } + + + private String normalizeUrl(String url) { + if (url != null && url.length() > 0) { + url = url.trim(); + if (!url.toLowerCase().startsWith("http://") && + !url.toLowerCase().startsWith("https://")) { + if (mIsSslConn) { + url = "https://" + url; + } else { + url = "http://" + url; + } + } + + // OC-208: Add suffix remote.php/webdav to normalize (OC-34) + url = trimUrlWebdav(url); + + if (url.endsWith("/")) { + url = url.substring(0, url.length() - 1); + } + + } + return (url != null ? url : ""); + } + + + private String trimUrlWebdav(String url){ + if(url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_4_0)){ + url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_4_0.length()); + } else if(url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_2_0)){ + url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_2_0.length()); + } else if (url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_1_2)){ + url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_1_2.length()); + } + return (url != null ? url : ""); + } + + + /** + * Chooses the right icon and text to show to the user for the received operation result. + * + * @param result Result of a remote operation performed in this activity + */ + private void updateServerStatusIconAndText(RemoteOperationResult result) { + mServerStatusIcon = R.drawable.common_error; // the most common case in the switch below + + switch (result.getCode()) { + case OK_SSL: + mServerStatusIcon = android.R.drawable.ic_secure; + mServerStatusText = R.string.auth_secure_connection; + break; + + case OK_NO_SSL: + case OK: + if (mHostUrlInput.getText().toString().trim().toLowerCase().startsWith("http://") ) { + mServerStatusText = R.string.auth_connection_established; + mServerStatusIcon = R.drawable.ic_ok; + } else { + mServerStatusText = R.string.auth_nossl_plain_ok_title; + mServerStatusIcon = android.R.drawable.ic_partial_secure; + } + break; + + case NO_NETWORK_CONNECTION: + mServerStatusIcon = R.drawable.no_network; + mServerStatusText = R.string.auth_no_net_conn_title; + break; + + case SSL_RECOVERABLE_PEER_UNVERIFIED: + mServerStatusText = R.string.auth_ssl_unverified_server_title; + break; + case BAD_OC_VERSION: + mServerStatusText = R.string.auth_bad_oc_version_title; + break; + case WRONG_CONNECTION: + mServerStatusText = R.string.auth_wrong_connection_title; + break; + case TIMEOUT: + mServerStatusText = R.string.auth_timeout_title; + break; + case INCORRECT_ADDRESS: + mServerStatusText = R.string.auth_incorrect_address_title; + break; + case SSL_ERROR: + mServerStatusText = R.string.auth_ssl_general_error_title; + break; + case UNAUTHORIZED: + mServerStatusText = R.string.auth_unauthorized; + break; + case HOST_NOT_AVAILABLE: + mServerStatusText = R.string.auth_unknown_host_title; + break; + case INSTANCE_NOT_CONFIGURED: + mServerStatusText = R.string.auth_not_configured_title; + break; + case FILE_NOT_FOUND: + mServerStatusText = R.string.auth_incorrect_path_title; + break; + case OAUTH2_ERROR: + mServerStatusText = R.string.auth_oauth_error; + break; + case OAUTH2_ERROR_ACCESS_DENIED: + mServerStatusText = R.string.auth_oauth_error_access_denied; + break; + case UNHANDLED_HTTP_CODE: + case UNKNOWN_ERROR: + mServerStatusText = R.string.auth_unknown_error_title; + break; + default: + mServerStatusText = 0; + mServerStatusIcon = 0; + } + } + + + /** + * Chooses the right icon and text to show to the user for the received operation result. + * + * @param result Result of a remote operation performed in this activity + */ + private void updateAuthStatusIconAndText(RemoteOperationResult result) { + mAuthStatusIcon = R.drawable.common_error; // the most common case in the switch below + + switch (result.getCode()) { + case OK_SSL: + mAuthStatusIcon = android.R.drawable.ic_secure; + mAuthStatusText = R.string.auth_secure_connection; + break; + + case OK_NO_SSL: + case OK: + if (mHostUrlInput.getText().toString().trim().toLowerCase().startsWith("http://") ) { + mAuthStatusText = R.string.auth_connection_established; + mAuthStatusIcon = R.drawable.ic_ok; + } else { + mAuthStatusText = R.string.auth_nossl_plain_ok_title; + mAuthStatusIcon = android.R.drawable.ic_partial_secure; + } + break; + + case NO_NETWORK_CONNECTION: + mAuthStatusIcon = R.drawable.no_network; + mAuthStatusText = R.string.auth_no_net_conn_title; + break; + + case SSL_RECOVERABLE_PEER_UNVERIFIED: + mAuthStatusText = R.string.auth_ssl_unverified_server_title; + break; + case BAD_OC_VERSION: + mAuthStatusText = R.string.auth_bad_oc_version_title; + break; + case WRONG_CONNECTION: + mAuthStatusText = R.string.auth_wrong_connection_title; + break; + case TIMEOUT: + mAuthStatusText = R.string.auth_timeout_title; + break; + case INCORRECT_ADDRESS: + mAuthStatusText = R.string.auth_incorrect_address_title; + break; + case SSL_ERROR: + mAuthStatusText = R.string.auth_ssl_general_error_title; + break; + case UNAUTHORIZED: + mAuthStatusText = R.string.auth_unauthorized; + break; + case HOST_NOT_AVAILABLE: + mAuthStatusText = R.string.auth_unknown_host_title; + break; + case INSTANCE_NOT_CONFIGURED: + mAuthStatusText = R.string.auth_not_configured_title; + break; + case FILE_NOT_FOUND: + mAuthStatusText = R.string.auth_incorrect_path_title; + break; + case OAUTH2_ERROR: + mAuthStatusText = R.string.auth_oauth_error; + break; + case OAUTH2_ERROR_ACCESS_DENIED: + mAuthStatusText = R.string.auth_oauth_error_access_denied; + break; + case ACCOUNT_NOT_NEW: + mAuthStatusText = R.string.auth_account_not_new; + break; + case ACCOUNT_NOT_THE_SAME: + mAuthStatusText = R.string.auth_account_not_the_same; + break; + case UNHANDLED_HTTP_CODE: + case UNKNOWN_ERROR: + mAuthStatusText = R.string.auth_unknown_error_title; + break; + default: + mAuthStatusText = 0; + mAuthStatusIcon = 0; + } + } + + + /** + * Processes the result of the request for and access token send + * to an OAuth authorization server. + * + * @param operation Operation performed requesting the access token. + * @param result Result of the operation. + */ + private void onGetOAuthAccessTokenFinish(OAuth2GetAccessToken operation, RemoteOperationResult result) { + try { + dismissDialog(DIALOG_OAUTH2_LOGIN_PROGRESS); + } catch (IllegalArgumentException e) { + // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens + } + + String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, mAuthTokenType); + if (result.isSuccess() && webdav_path != null) { + /// be gentle with the user + showDialog(DIALOG_LOGIN_PROGRESS); + + /// time to test the retrieved access token on the ownCloud server + mAuthToken = ((OAuth2GetAccessToken)operation).getResultTokenMap().get(OAuth2Constants.KEY_ACCESS_TOKEN); + Log_OC.d(TAG, "Got ACCESS TOKEN: " + mAuthToken); + mAuthCheckOperation = new ExistenceCheckOperation("", this, false); + WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, true); + client.setBearerCredentials(mAuthToken); + mAuthCheckOperation.execute(client, this, mHandler); + + } else { + updateAuthStatusIconAndText(result); + showAuthStatus(); + Log_OC.d(TAG, "Access failed: " + result.getLogMessage()); + } + } + + + /** + * Processes the result of the access check performed to try the user credentials. + * + * Creates a new account through the AccountManager. + * + * @param operation Access check performed. + * @param result Result of the operation. + */ + private void onAuthorizationCheckFinish(ExistenceCheckOperation operation, RemoteOperationResult result) { + try { + dismissDialog(DIALOG_LOGIN_PROGRESS); + } catch (IllegalArgumentException e) { + // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens + } + + if (result.isSuccess()) { + Log_OC.d(TAG, "Successful access - time to save the account"); + + boolean success = false; + if (mAction == ACTION_CREATE) { + success = createAccount(); + + } else { + success = updateToken(); + } + + if (success) { + finish(); + } + + } else if (result.isServerFail() || result.isException()) { + /// if server fail or exception in authorization, the UI is updated as when a server check failed + mServerIsChecked = true; + mServerIsValid = false; + mIsSslConn = false; + mOcServerChkOperation = null; + mDiscoveredVersion = null; + mHostBaseUrl = normalizeUrl(mHostUrlInput.getText().toString()); + + // update status icon and text + updateServerStatusIconAndText(result); + showServerStatus(); + mAuthStatusIcon = 0; + mAuthStatusText = 0; + showAuthStatus(); + + // update input controls state + showRefreshButton(); + mOkButton.setEnabled(false); + + // very special case (TODO: move to a common place for all the remote operations) (dangerous here?) + if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) { + mLastSslUntrustedServerResult = result; + showDialog(DIALOG_SSL_VALIDATOR); + } + + } else { // authorization fail due to client side - probably wrong credentials + updateAuthStatusIconAndText(result); + showAuthStatus(); + Log_OC.d(TAG, "Access failed: " + result.getLogMessage()); + } + + } + + + /** + * Sets the proper response to get that the Account Authenticator that started this activity saves + * a new authorization token for mAccount. + */ + private boolean updateToken() { + Bundle response = new Bundle(); + response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name); + response.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type); + + if (MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType)) { + response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken); + // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention + mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken); + + } else if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType)) { + String username = getUserNameForSamlSso(); + if (!mUsernameInput.getText().toString().equals(username)) { + // fail - not a new account, but an existing one; disallow + RemoteOperationResult result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_THE_SAME); + updateAuthStatusIconAndText(result); + showAuthStatus(); + Log_OC.d(TAG, result.getLogMessage()); + + return false; + } + + response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken); + // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention + mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken); + + } else { + response.putString(AccountManager.KEY_AUTHTOKEN, mPasswordInput.getText().toString()); + mAccountMgr.setPassword(mAccount, mPasswordInput.getText().toString()); + } + setAccountAuthenticatorResult(response); + + return true; + } + + + /** + * Creates a new account through the Account Authenticator that started this activity. + * + * This makes the account permanent. + * + * TODO Decide how to name the OAuth accounts + */ + private boolean createAccount() { + /// create and save new ownCloud account + boolean isOAuth = MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType); + boolean isSaml = MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType); + + Uri uri = Uri.parse(mHostBaseUrl); + String username = mUsernameInput.getText().toString().trim(); + if (isSaml) { + username = getUserNameForSamlSso(); + + } else if (isOAuth) { + username = "OAuth_user" + (new java.util.Random(System.currentTimeMillis())).nextLong(); + } + String accountName = username + "@" + uri.getHost(); + if (uri.getPort() >= 0) { + accountName += ":" + uri.getPort(); + } + mAccount = new Account(accountName, MainApp.getAccountType()); + if (AccountUtils.exists(mAccount, getApplicationContext())) { + // fail - not a new account, but an existing one; disallow + RemoteOperationResult result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_NEW); + updateAuthStatusIconAndText(result); + showAuthStatus(); + Log_OC.d(TAG, result.getLogMessage()); + return false; + + } else { + + if (isOAuth || isSaml) { + mAccountMgr.addAccountExplicitly(mAccount, "", null); // with external authorizations, the password is never input in the app + } else { + mAccountMgr.addAccountExplicitly(mAccount, mPasswordInput.getText().toString(), null); + } + + /// add the new account as default in preferences, if there is none already + Account defaultAccount = AccountUtils.getCurrentOwnCloudAccount(this); + if (defaultAccount == null) { + SharedPreferences.Editor editor = PreferenceManager + .getDefaultSharedPreferences(this).edit(); + editor.putString("select_oc_account", accountName); + editor.commit(); + } + + /// prepare result to return to the Authenticator + // TODO check again what the Authenticator makes with it; probably has the same effect as addAccountExplicitly, but it's not well done + final Intent intent = new Intent(); + intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, MainApp.getAccountType()); + intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mAccount.name); + /*if (!isOAuth) + intent.putExtra(AccountManager.KEY_AUTHTOKEN, MainApp.getAccountType()); */ + intent.putExtra(AccountManager.KEY_USERDATA, username); + if (isOAuth || isSaml) { + mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken); + } + /// add user data to the new account; TODO probably can be done in the last parameter addAccountExplicitly, or in KEY_USERDATA + mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION, mDiscoveredVersion.toString()); + mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL, mHostBaseUrl); + if (isSaml) { + mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_SAML_WEB_SSO, "TRUE"); + } else if (isOAuth) { + mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2, "TRUE"); + } + + setAccountAuthenticatorResult(intent.getExtras()); + setResult(RESULT_OK, intent); + + /// immediately request for the synchronization of the new account + Bundle bundle = new Bundle(); + bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + ContentResolver.requestSync(mAccount, MainApp.getAuthTokenType(), bundle); + syncAccount(); +// Bundle bundle = new Bundle(); +// bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); +// ContentResolver.requestSync(mAccount, MainApp.getAuthTokenType(), bundle); + return true; + } + } + + + private String getUserNameForSamlSso() { + if (mAuthToken != null) { + String [] cookies = mAuthToken.split(";"); + for (int i=0; i 0) { + Log_OC.d(TAG, "Successful SSO - time to save the account"); + onSamlDialogSuccess(sessionCookies); + Fragment fd = getSupportFragmentManager().findFragmentByTag(TAG_SAML_DIALOG); + if (fd != null && fd instanceof SherlockDialogFragment) { + Dialog d = ((SherlockDialogFragment)fd).getDialog(); + if (d != null && d.isShowing()) { + d.dismiss(); + } + } + + } else { + // TODO - show fail + Log_OC.d(TAG, "SSO failed"); + } + + } + + /** Show auth_message + * + * @param message + */ + private void showAuthMessage(String message) { + mAuthMessage.setVisibility(View.VISIBLE); + mAuthMessage.setText(message); + } + + private void hideAuthMessage() { + mAuthMessage.setVisibility(View.GONE); + } + + private void syncAccount(){ + /// immediately request for the synchronization of the new account + Bundle bundle = new Bundle(); + bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + ContentResolver.requestSync(mAccount, MainApp.getAuthTokenType(), bundle); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType) && + mHostUrlInput.hasFocus() && event.getAction() == MotionEvent.ACTION_DOWN) { + checkOcServer(); + } + return super.onTouchEvent(event); + } +} diff --git a/src/com/owncloud/android/authentication/OAuth2Constants.java b/src/com/owncloud/android/authentication/OAuth2Constants.java new file mode 100644 index 00000000..f96b6278 --- /dev/null +++ b/src/com/owncloud/android/authentication/OAuth2Constants.java @@ -0,0 +1,53 @@ +/* ownCloud Android client application + * 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.authentication; + +/** + * Constant values for OAuth 2 protocol. + * + * Includes required and optional parameter NAMES used in the 'authorization code' grant type. + * + * @author David A. Velasco + */ + +public class OAuth2Constants { + + /// Parameters to send to the Authorization Endpoint + public static final String KEY_RESPONSE_TYPE = "response_type"; + public static final String KEY_REDIRECT_URI = "redirect_uri"; + public static final String KEY_CLIENT_ID = "client_id"; + public static final String KEY_SCOPE = "scope"; + public static final String KEY_STATE = "state"; + + /// Additional parameters to send to the Token Endpoint + public static final String KEY_GRANT_TYPE = "grant_type"; + public static final String KEY_CODE = "code"; + + /// Parameters received in an OK response from the Token Endpoint + public static final String KEY_ACCESS_TOKEN = "access_token"; + public static final String KEY_TOKEN_TYPE = "token_type"; + public static final String KEY_EXPIRES_IN = "expires_in"; + public static final String KEY_REFRESH_TOKEN = "refresh_token"; + + /// Parameters in an ERROR response + public static final String KEY_ERROR = "error"; + public static final String KEY_ERROR_DESCRIPTION = "error_description"; + public static final String KEY_ERROR_URI = "error_uri"; + public static final String VALUE_ERROR_ACCESS_DENIED = "access_denied"; + +} diff --git a/src/com/owncloud/android/authentication/SsoWebViewClient.java b/src/com/owncloud/android/authentication/SsoWebViewClient.java new file mode 100644 index 00000000..5c97931e --- /dev/null +++ b/src/com/owncloud/android/authentication/SsoWebViewClient.java @@ -0,0 +1,176 @@ +/* ownCloud Android client application + * 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.authentication; + +import java.lang.ref.WeakReference; + +import com.owncloud.android.Log_OC; + + +import android.graphics.Bitmap; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import android.webkit.CookieManager; +import android.webkit.WebView; +import android.webkit.WebViewClient; + + +/** + * Custom {@link WebViewClient} client aimed to catch the end of a single-sign-on process + * running in the {@link WebView} that is attached to. + * + * Assumes that the single-sign-on is kept thanks to a cookie set at the end of the + * authentication process. + * + * @author David A. Velasco + */ +public class SsoWebViewClient extends WebViewClient { + + private static final String TAG = SsoWebViewClient.class.getSimpleName(); + + public interface SsoWebViewClientListener { + public void onSsoFinished(String sessionCookie); + } + + private Handler mListenerHandler; + private WeakReference mListenerRef; + private String mTargetUrl; + private String mLastReloadedUrlAtError; + + public SsoWebViewClient (Handler listenerHandler, SsoWebViewClientListener listener) { + mListenerHandler = listenerHandler; + mListenerRef = new WeakReference(listener); + mTargetUrl = "fake://url.to.be.set"; + mLastReloadedUrlAtError = null; + } + + public String getTargetUrl() { + return mTargetUrl; + } + + public void setTargetUrl(String targetUrl) { + mTargetUrl = targetUrl; + } + + @Override + public void onPageStarted (WebView view, String url, Bitmap favicon) { + Log_OC.d(TAG, "onPageStarted : " + url); + super.onPageStarted(view, url, favicon); + } + + @Override + public void onFormResubmission (WebView view, Message dontResend, Message resend) { + Log_OC.d(TAG, "onFormResubMission "); + + // necessary to grant reload of last page when device orientation is changed after sending a form + resend.sendToTarget(); + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + return false; + } + + @Override + public void onReceivedError (WebView view, int errorCode, String description, String failingUrl) { + Log_OC.e(TAG, "onReceivedError : " + failingUrl + ", code " + errorCode + ", description: " + description); + if (!failingUrl.equals(mLastReloadedUrlAtError)) { + view.reload(); + mLastReloadedUrlAtError = failingUrl; + } else { + mLastReloadedUrlAtError = null; + super.onReceivedError(view, errorCode, description, failingUrl); + } + } + + @Override + public void onPageFinished (WebView view, String url) { + Log_OC.d(TAG, "onPageFinished : " + url); + mLastReloadedUrlAtError = null; + if (url.startsWith(mTargetUrl)) { + view.setVisibility(View.GONE); + CookieManager cookieManager = CookieManager.getInstance(); + final String cookies = cookieManager.getCookie(url); + //Log_OC.d(TAG, "Cookies: " + cookies); + if (mListenerHandler != null && mListenerRef != null) { + // this is good idea because onPageFinished is not running in the UI thread + mListenerHandler.post(new Runnable() { + @Override + public void run() { + SsoWebViewClientListener listener = mListenerRef.get(); + if (listener != null) { + listener.onSsoFinished(cookies); + } + } + }); + } + } + + } + + /* + @Override + public void doUpdateVisitedHistory (WebView view, String url, boolean isReload) { + Log_OC.d(TAG, "doUpdateVisitedHistory : " + url); + } + + @Override + public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) { + Log_OC.d(TAG, "onReceivedSslError : " + error); + } + + @Override + public void onReceivedHttpAuthRequest (WebView view, HttpAuthHandler handler, String host, String realm) { + Log_OC.d(TAG, "onReceivedHttpAuthRequest : " + host); + } + + @Override + public WebResourceResponse shouldInterceptRequest (WebView view, String url) { + Log_OC.d(TAG, "shouldInterceptRequest : " + url); + return null; + } + + @Override + public void onLoadResource (WebView view, String url) { + Log_OC.d(TAG, "onLoadResource : " + url); + } + + @Override + public void onReceivedLoginRequest (WebView view, String realm, String account, String args) { + Log_OC.d(TAG, "onReceivedLoginRequest : " + realm + ", " + account + ", " + args); + } + + @Override + public void onScaleChanged (WebView view, float oldScale, float newScale) { + Log_OC.d(TAG, "onScaleChanged : " + oldScale + " -> " + newScale); + super.onScaleChanged(view, oldScale, newScale); + } + + @Override + public void onUnhandledKeyEvent (WebView view, KeyEvent event) { + Log_OC.d(TAG, "onUnhandledKeyEvent : " + event); + } + + @Override + public boolean shouldOverrideKeyEvent (WebView view, KeyEvent event) { + Log_OC.d(TAG, "shouldOverrideKeyEvent : " + event); + return false; + } + */ +} diff --git a/src/com/owncloud/android/datamodel/DataStorageManager.java b/src/com/owncloud/android/datamodel/DataStorageManager.java new file mode 100644 index 00000000..133ab8de --- /dev/null +++ b/src/com/owncloud/android/datamodel/DataStorageManager.java @@ -0,0 +1,52 @@ +/* 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.datamodel; + +import java.util.List; +import java.util.Vector; + +public interface DataStorageManager { + + public static final int ROOT_PARENT_ID = 0; + + public OCFile getFileByPath(String path); + + public OCFile getFileById(long id); + + public boolean fileExists(String path); + + public boolean fileExists(long id); + + public boolean saveFile(OCFile file); + + public void saveFiles(List files); + + public Vector getDirectoryContent(OCFile f); + + public void removeFile(OCFile file, boolean removeLocalCopy); + + public void removeDirectory(OCFile dir, boolean removeDBData, boolean removeLocalContent); + + public void moveDirectory(OCFile dir, String newPath); + + public Vector getDirectoryImages(OCFile mParentFolder); + + public void calculateFolderSize(long id); + +} diff --git a/src/com/owncloud/android/datamodel/FileDataStorageManager.java b/src/com/owncloud/android/datamodel/FileDataStorageManager.java new file mode 100644 index 00000000..9933e360 --- /dev/null +++ b/src/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -0,0 +1,690 @@ +/* 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.datamodel; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Vector; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; +import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; +import com.owncloud.android.utils.FileStorageUtils; + + +import android.accounts.Account; +import android.content.ContentProviderClient; +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.OperationApplicationException; +import android.database.Cursor; +import android.net.Uri; +import android.os.RemoteException; + +public class FileDataStorageManager implements DataStorageManager { + + private ContentResolver mContentResolver; + private ContentProviderClient mContentProvider; + private Account mAccount; + + private static String TAG = "FileDataStorageManager"; + + public FileDataStorageManager(Account account, ContentResolver cr) { + mContentProvider = null; + mContentResolver = cr; + mAccount = account; + } + + public FileDataStorageManager(Account account, ContentProviderClient cp) { + mContentProvider = cp; + mContentResolver = null; + mAccount = account; + } + + @Override + public OCFile getFileByPath(String path) { + Cursor c = getCursorForValue(ProviderTableMeta.FILE_PATH, path); + OCFile file = null; + if (c.moveToFirst()) { + file = createFileInstance(c); + } + c.close(); + if (file == null && OCFile.PATH_SEPARATOR.equals(path)) { + return createRootDir(); // root should always exist + } + return file; + } + + + private OCFile createRootDir() { + OCFile file = new OCFile(OCFile.PATH_SEPARATOR); + file.setMimetype("DIR"); + file.setParentId(DataStorageManager.ROOT_PARENT_ID); + saveFile(file); + return file; + } + + @Override + public OCFile getFileById(long id) { + Cursor c = getCursorForValue(ProviderTableMeta._ID, String.valueOf(id)); + OCFile file = null; + if (c.moveToFirst()) { + file = createFileInstance(c); + } + c.close(); + return file; + } + + public OCFile getFileByLocalPath(String path) { + Cursor c = getCursorForValue(ProviderTableMeta.FILE_STORAGE_PATH, path); + OCFile file = null; + if (c.moveToFirst()) { + file = createFileInstance(c); + } + c.close(); + return file; + } + + @Override + public boolean fileExists(long id) { + return fileExists(ProviderTableMeta._ID, String.valueOf(id)); + } + + @Override + public boolean fileExists(String path) { + return fileExists(ProviderTableMeta.FILE_PATH, path); + } + + @Override + public boolean saveFile(OCFile file) { + boolean overriden = false; + ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp()); + cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, file.getModificationTimestampAtLastSyncForData()); + cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp()); + cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength()); + cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype()); + cv.put(ProviderTableMeta.FILE_NAME, file.getFileName()); + if (file.getParentId() != 0) + cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId()); + cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath()); + if (!file.isDirectory()) + cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath()); + cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name); + cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties()); + cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData()); + cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.keepInSync() ? 1 : 0); + + boolean sameRemotePath = fileExists(file.getRemotePath()); + boolean changesSizeOfAncestors = false; + if (sameRemotePath || + fileExists(file.getFileId()) ) { // for renamed files; no more delete and create + + OCFile oldFile = null; + if (sameRemotePath) { + oldFile = getFileByPath(file.getRemotePath()); + file.setFileId(oldFile.getFileId()); + } else { + oldFile = getFileById(file.getFileId()); + } + changesSizeOfAncestors = (oldFile.getFileLength() != file.getFileLength()); + + overriden = true; + if (getContentResolver() != null) { + getContentResolver().update(ProviderTableMeta.CONTENT_URI, cv, + ProviderTableMeta._ID + "=?", + new String[] { String.valueOf(file.getFileId()) }); + } else { + try { + getContentProvider().update(ProviderTableMeta.CONTENT_URI, + cv, ProviderTableMeta._ID + "=?", + new String[] { String.valueOf(file.getFileId()) }); + } catch (RemoteException e) { + Log_OC.e(TAG, + "Fail to insert insert file to database " + + e.getMessage()); + } + } + } else { + changesSizeOfAncestors = true; + Uri result_uri = null; + if (getContentResolver() != null) { + result_uri = getContentResolver().insert( + ProviderTableMeta.CONTENT_URI_FILE, cv); + } else { + try { + result_uri = getContentProvider().insert( + ProviderTableMeta.CONTENT_URI_FILE, cv); + } catch (RemoteException e) { + Log_OC.e(TAG, + "Fail to insert insert file to database " + + e.getMessage()); + } + } + if (result_uri != null) { + long new_id = Long.parseLong(result_uri.getPathSegments() + .get(1)); + file.setFileId(new_id); + } + } + + if (file.isDirectory()) { + calculateFolderSize(file.getFileId()); + if (file.needsUpdatingWhileSaving()) { + for (OCFile f : getDirectoryContent(file)) + saveFile(f); + } + } + + if (changesSizeOfAncestors || file.isDirectory()) { + updateSizesToTheRoot(file.getParentId()); + } + + return overriden; + } + + + @Override + public void saveFiles(List files) { + + Iterator filesIt = files.iterator(); + ArrayList operations = new ArrayList(files.size()); + OCFile file = null; + + // prepare operations to perform + while (filesIt.hasNext()) { + file = filesIt.next(); + ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp()); + cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, file.getModificationTimestampAtLastSyncForData()); + cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp()); + cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength()); + cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype()); + cv.put(ProviderTableMeta.FILE_NAME, file.getFileName()); + if (file.getParentId() != 0) + cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId()); + cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath()); + if (!file.isDirectory()) + cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath()); + cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name); + cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties()); + cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData()); + cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.keepInSync() ? 1 : 0); + + if (fileExists(file.getRemotePath())) { + OCFile oldFile = getFileByPath(file.getRemotePath()); + file.setFileId(oldFile.getFileId()); + + if (file.isDirectory()) { + cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, oldFile.getFileLength()); + file.setFileLength(oldFile.getFileLength()); + } + + operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). + withValues(cv). + withSelection( ProviderTableMeta._ID + "=?", + new String[] { String.valueOf(file.getFileId()) }) + .build()); + + } else if (fileExists(file.getFileId())) { + OCFile oldFile = getFileById(file.getFileId()); + if (file.getStoragePath() == null && oldFile.getStoragePath() != null) + file.setStoragePath(oldFile.getStoragePath()); + + if (!file.isDirectory()) + cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath()); + else { + cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, oldFile.getFileLength()); + file.setFileLength(oldFile.getFileLength()); + } + + operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). + withValues(cv). + withSelection( ProviderTableMeta._ID + "=?", + new String[] { String.valueOf(file.getFileId()) }) + .build()); + + } else { + operations.add(ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI).withValues(cv).build()); + } + } + + // apply operations in batch + ContentProviderResult[] results = null; + try { + if (getContentResolver() != null) { + results = getContentResolver().applyBatch(MainApp.getAuthority(), operations); + + } else { + results = getContentProvider().applyBatch(operations); + } + + } catch (OperationApplicationException e) { + Log_OC.e(TAG, "Fail to update/insert list of files to database " + e.getMessage()); + + } catch (RemoteException e) { + Log_OC.e(TAG, "Fail to update/insert list of files to database " + e.getMessage()); + } + + // update new id in file objects for insertions + if (results != null) { + long newId; + for (int i=0; i getDirectoryContent(OCFile f) { + if (f != null && f.isDirectory() && f.getFileId() != -1) { + return getDirectoryContent(f.getFileId()); + + } else { + return new Vector(); + } + } + + private Vector getDirectoryContent(long parentId) { + + Vector ret = new Vector(); + + Uri req_uri = Uri.withAppendedPath( + ProviderTableMeta.CONTENT_URI_DIR, + String.valueOf(parentId)); + Cursor c = null; + + if (getContentProvider() != null) { + try { + c = getContentProvider().query(req_uri, null, + ProviderTableMeta.FILE_PARENT + "=?" , + new String[] { String.valueOf(parentId)}, null); + } catch (RemoteException e) { + Log_OC.e(TAG, e.getMessage()); + return ret; + } + } else { + c = getContentResolver().query(req_uri, null, + ProviderTableMeta.FILE_PARENT + "=?" , + new String[] { String.valueOf(parentId)}, null); + } + + if (c.moveToFirst()) { + do { + OCFile child = createFileInstance(c); + ret.add(child); + } while (c.moveToNext()); + } + + c.close(); + + Collections.sort(ret); + + return ret; + } + + + + private boolean fileExists(String cmp_key, String value) { + Cursor c; + if (getContentResolver() != null) { + c = getContentResolver() + .query(ProviderTableMeta.CONTENT_URI, + null, + cmp_key + "=? AND " + + ProviderTableMeta.FILE_ACCOUNT_OWNER + + "=?", + new String[] { value, mAccount.name }, null); + } else { + try { + c = getContentProvider().query( + ProviderTableMeta.CONTENT_URI, + null, + cmp_key + "=? AND " + + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?", + new String[] { value, mAccount.name }, null); + } catch (RemoteException e) { + Log_OC.e(TAG, + "Couldn't determine file existance, assuming non existance: " + + e.getMessage()); + return false; + } + } + boolean retval = c.moveToFirst(); + c.close(); + return retval; + } + + private Cursor getCursorForValue(String key, String value) { + Cursor c = null; + if (getContentResolver() != null) { + c = getContentResolver() + .query(ProviderTableMeta.CONTENT_URI, + null, + key + "=? AND " + + ProviderTableMeta.FILE_ACCOUNT_OWNER + + "=?", + new String[] { value, mAccount.name }, null); + } else { + try { + c = getContentProvider().query( + ProviderTableMeta.CONTENT_URI, + null, + key + "=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER + + "=?", new String[] { value, mAccount.name }, + null); + } catch (RemoteException e) { + Log_OC.e(TAG, "Could not get file details: " + e.getMessage()); + c = null; + } + } + return c; + } + + private OCFile createFileInstance(Cursor c) { + OCFile file = null; + if (c != null) { + file = new OCFile(c.getString(c + .getColumnIndex(ProviderTableMeta.FILE_PATH))); + file.setFileId(c.getLong(c.getColumnIndex(ProviderTableMeta._ID))); + file.setParentId(c.getLong(c + .getColumnIndex(ProviderTableMeta.FILE_PARENT))); + file.setMimetype(c.getString(c + .getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE))); + if (!file.isDirectory()) { + file.setStoragePath(c.getString(c + .getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH))); + if (file.getStoragePath() == null) { + // try to find existing file and bind it with current account; - with the current update of SynchronizeFolderOperation, this won't be necessary anymore after a full synchronization of the account + File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); + if (f.exists()) { + file.setStoragePath(f.getAbsolutePath()); + file.setLastSyncDateForData(f.lastModified()); + } + } + } + file.setFileLength(c.getLong(c + .getColumnIndex(ProviderTableMeta.FILE_CONTENT_LENGTH))); + file.setCreationTimestamp(c.getLong(c + .getColumnIndex(ProviderTableMeta.FILE_CREATION))); + file.setModificationTimestamp(c.getLong(c + .getColumnIndex(ProviderTableMeta.FILE_MODIFIED))); + file.setModificationTimestampAtLastSyncForData(c.getLong(c + .getColumnIndex(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA))); + file.setLastSyncDateForProperties(c.getLong(c + .getColumnIndex(ProviderTableMeta.FILE_LAST_SYNC_DATE))); + file.setLastSyncDateForData(c.getLong(c. + getColumnIndex(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA))); + file.setKeepInSync(c.getInt( + c.getColumnIndex(ProviderTableMeta.FILE_KEEP_IN_SYNC)) == 1 ? true : false); + } + return file; + } + + @Override + public void removeFile(OCFile file, boolean removeLocalCopy) { + Uri file_uri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, ""+file.getFileId()); + if (getContentProvider() != null) { + try { + getContentProvider().delete(file_uri, + ProviderTableMeta.FILE_ACCOUNT_OWNER+"=?", + new String[]{mAccount.name}); + } catch (RemoteException e) { + e.printStackTrace(); + } + } else { + getContentResolver().delete(file_uri, + ProviderTableMeta.FILE_ACCOUNT_OWNER+"=?", + new String[]{mAccount.name}); + } + if (file.isDown() && removeLocalCopy) { + new File(file.getStoragePath()).delete(); + } + if (file.isDirectory() && removeLocalCopy) { + File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); + if (f.exists() && f.isDirectory() && (f.list() == null || f.list().length == 0)) { + f.delete(); + } + } + + if (file.getFileLength() > 0) { + updateSizesToTheRoot(file.getParentId()); + } + } + + @Override + public void removeDirectory(OCFile dir, boolean removeDBData, boolean removeLocalContent) { + // TODO consider possible failures + if (dir != null && dir.isDirectory() && dir.getFileId() != -1) { + Vector children = getDirectoryContent(dir); + if (children.size() > 0) { + OCFile child = null; + for (int i=0; i 0) { + updateSizesToTheRoot(dir.getParentId()); + } + } + } + + + /** + * Updates database for a folder that was moved to a different location. + * + * TODO explore better (faster) implementations + * TODO throw exceptions up ! + */ + @Override + public void moveDirectory(OCFile dir, String newPath) { + // TODO check newPath + + if (dir != null && dir.isDirectory() && dir.fileExists() && !dir.getFileName().equals(OCFile.PATH_SEPARATOR)) { + /// 1. get all the descendants of 'dir' in a single QUERY (including 'dir') + Cursor c = null; + if (getContentProvider() != null) { + try { + c = getContentProvider().query(ProviderTableMeta.CONTENT_URI, + null, + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ? ", + new String[] { mAccount.name, dir.getRemotePath() + "%" }, ProviderTableMeta.FILE_PATH + " ASC "); + } catch (RemoteException e) { + Log_OC.e(TAG, e.getMessage()); + } + } else { + c = getContentResolver().query(ProviderTableMeta.CONTENT_URI, + null, + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ? ", + new String[] { mAccount.name, dir.getRemotePath() + "%" }, ProviderTableMeta.FILE_PATH + " ASC "); + } + + /// 2. prepare a batch of update operations to change all the descendants + ArrayList operations = new ArrayList(c.getCount()); + int lengthOfOldPath = dir.getRemotePath().length(); + String defaultSavePath = FileStorageUtils.getSavePath(mAccount.name); + int lengthOfOldStoragePath = defaultSavePath.length() + lengthOfOldPath; + if (c.moveToFirst()) { + do { + ContentValues cv = new ContentValues(); // don't take the constructor out of the loop and clear the object + OCFile child = createFileInstance(c); + cv.put(ProviderTableMeta.FILE_PATH, newPath + child.getRemotePath().substring(lengthOfOldPath)); + if (child.getStoragePath() != null && child.getStoragePath().startsWith(defaultSavePath)) { + cv.put(ProviderTableMeta.FILE_STORAGE_PATH, defaultSavePath + newPath + child.getStoragePath().substring(lengthOfOldStoragePath)); + } + operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). + withValues(cv). + withSelection( ProviderTableMeta._ID + "=?", + new String[] { String.valueOf(child.getFileId()) }) + .build()); + } while (c.moveToNext()); + } + c.close(); + + /// 3. apply updates in batch + try { + if (getContentResolver() != null) { + getContentResolver().applyBatch(MainApp.getAuthority(), operations); + + } else { + getContentProvider().applyBatch(operations); + } + + } catch (OperationApplicationException e) { + Log_OC.e(TAG, "Fail to update descendants of " + dir.getFileId() + " in database", e); + + } catch (RemoteException e) { + Log_OC.e(TAG, "Fail to update desendants of " + dir.getFileId() + " in database", e); + } + + } + } + + @Override + public Vector getDirectoryImages(OCFile directory) { + Vector ret = new Vector(); + if (directory != null) { + // TODO better implementation, filtering in the access to database (if possible) instead of here + Vector tmp = getDirectoryContent(directory); + OCFile current = null; + for (int i=0; i files = getDirectoryContent(id); + + for (OCFile f: files) + { + folderSize = folderSize + f.getFileLength(); + } + + updateSize(id, folderSize); + } + + /** + * Update the size value of an OCFile in DB + */ + private int updateSize(long id, long size) { + ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, size); + int result = -1; + if (getContentResolver() != null) { + result = getContentResolver().update(ProviderTableMeta.CONTENT_URI, cv, ProviderTableMeta._ID + "=?", + new String[] { String.valueOf(id) }); + } else { + try { + result = getContentProvider().update(ProviderTableMeta.CONTENT_URI, cv, ProviderTableMeta._ID + "=?", + new String[] { String.valueOf(id) }); + } catch (RemoteException e) { + Log_OC.e(TAG,"Fail to update size column into database " + e.getMessage()); + } + } + return result; + } + + /** + * Update the size of a subtree of folder from a file to the root + * @param parentId: parent of the file + */ + private void updateSizesToTheRoot(long parentId) { + + OCFile file; + + while (parentId != 0) { + + // Update the size of the parent + calculateFolderSize(parentId); + + // search the next parent + file = getFileById(parentId); + parentId = file.getParentId(); + + } + + } + +} diff --git a/src/com/owncloud/android/datamodel/OCFile.java b/src/com/owncloud/android/datamodel/OCFile.java new file mode 100644 index 00000000..76fb6b2f --- /dev/null +++ b/src/com/owncloud/android/datamodel/OCFile.java @@ -0,0 +1,486 @@ +/* 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.datamodel; + +import java.io.File; + +import com.owncloud.android.Log_OC; + + +import android.os.Parcel; +import android.os.Parcelable; +import android.webkit.MimeTypeMap; + +public class OCFile implements Parcelable, Comparable { + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public OCFile createFromParcel(Parcel source) { + return new OCFile(source); + } + + @Override + public OCFile[] newArray(int size) { + return new OCFile[size]; + } + }; + + public static final String PATH_SEPARATOR = "/"; + + private static final String TAG = OCFile.class.getSimpleName(); + + private long mId; + private long mParentId; + private long mLength; + private long mCreationTimestamp; + private long mModifiedTimestamp; + private long mModifiedTimestampAtLastSyncForData; + private String mRemotePath; + private String mLocalPath; + private String mMimeType; + private boolean mNeedsUpdating; + private long mLastSyncDateForProperties; + private long mLastSyncDateForData; + private boolean mKeepInSync; + + private String mEtag; + + /** + * Create new {@link OCFile} with given path. + * + * The path received must be URL-decoded. Path separator must be OCFile.PATH_SEPARATOR, and it must be the first character in 'path'. + * + * @param path The remote path of the file. + */ + public OCFile(String path) { + resetData(); + mNeedsUpdating = false; + if (path == null || path.length() <= 0 || !path.startsWith(PATH_SEPARATOR)) { + throw new IllegalArgumentException("Trying to create a OCFile with a non valid remote path: " + path); + } + mRemotePath = path; + } + + /** + * Reconstruct from parcel + * + * @param source The source parcel + */ + private OCFile(Parcel source) { + mId = source.readLong(); + mParentId = source.readLong(); + mLength = source.readLong(); + mCreationTimestamp = source.readLong(); + mModifiedTimestamp = source.readLong(); + mModifiedTimestampAtLastSyncForData = source.readLong(); + mRemotePath = source.readString(); + mLocalPath = source.readString(); + mMimeType = source.readString(); + mNeedsUpdating = source.readInt() == 0; + mKeepInSync = source.readInt() == 1; + mLastSyncDateForProperties = source.readLong(); + mLastSyncDateForData = source.readLong(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mId); + dest.writeLong(mParentId); + dest.writeLong(mLength); + dest.writeLong(mCreationTimestamp); + dest.writeLong(mModifiedTimestamp); + dest.writeLong(mModifiedTimestampAtLastSyncForData); + dest.writeString(mRemotePath); + dest.writeString(mLocalPath); + dest.writeString(mMimeType); + dest.writeInt(mNeedsUpdating ? 1 : 0); + dest.writeInt(mKeepInSync ? 1 : 0); + dest.writeLong(mLastSyncDateForProperties); + dest.writeLong(mLastSyncDateForData); + } + + /** + * Gets the ID of the file + * + * @return the file ID + */ + public long getFileId() { + return mId; + } + + /** + * Returns the remote path of the file on ownCloud + * + * @return The remote path to the file + */ + public String getRemotePath() { + return mRemotePath; + } + + /** + * Can be used to check, whether or not this file exists in the database + * already + * + * @return true, if the file exists in the database + */ + public boolean fileExists() { + return mId != -1; + } + + /** + * Use this to find out if this file is a Directory + * + * @return true if it is a directory + */ + public boolean isDirectory() { + return mMimeType != null && mMimeType.equals("DIR"); + } + + /** + * Use this to check if this file is available locally + * + * @return true if it is + */ + public boolean isDown() { + if (mLocalPath != null && mLocalPath.length() > 0) { + File file = new File(mLocalPath); + return (file.exists()); + } + return false; + } + + /** + * The path, where the file is stored locally + * + * @return The local path to the file + */ + public String getStoragePath() { + return mLocalPath; + } + + /** + * Can be used to set the path where the file is stored + * + * @param storage_path to set + */ + public void setStoragePath(String storage_path) { + mLocalPath = storage_path; + } + + /** + * Get a UNIX timestamp of the file creation time + * + * @return A UNIX timestamp of the time that file was created + */ + public long getCreationTimestamp() { + return mCreationTimestamp; + } + + /** + * Set a UNIX timestamp of the time the file was created + * + * @param creation_timestamp to set + */ + public void setCreationTimestamp(long creation_timestamp) { + mCreationTimestamp = creation_timestamp; + } + + /** + * Get a UNIX timestamp of the file modification time. + * + * @return A UNIX timestamp of the modification time, corresponding to the value returned by the server + * in the last synchronization of the properties of this file. + */ + public long getModificationTimestamp() { + return mModifiedTimestamp; + } + + /** + * Set a UNIX timestamp of the time the time the file was modified. + * + * To update with the value returned by the server in every synchronization of the properties + * of this file. + * + * @param modification_timestamp to set + */ + public void setModificationTimestamp(long modification_timestamp) { + mModifiedTimestamp = modification_timestamp; + } + + + /** + * Get a UNIX timestamp of the file modification time. + * + * @return A UNIX timestamp of the modification time, corresponding to the value returned by the server + * in the last synchronization of THE CONTENTS of this file. + */ + public long getModificationTimestampAtLastSyncForData() { + return mModifiedTimestampAtLastSyncForData; + } + + /** + * Set a UNIX timestamp of the time the time the file was modified. + * + * To update with the value returned by the server in every synchronization of THE CONTENTS + * of this file. + * + * @param modification_timestamp to set + */ + public void setModificationTimestampAtLastSyncForData(long modificationTimestamp) { + mModifiedTimestampAtLastSyncForData = modificationTimestamp; + } + + + + /** + * Returns the filename and "/" for the root directory + * + * @return The name of the file + */ + public String getFileName() { + File f = new File(getRemotePath()); + return f.getName().length() == 0 ? PATH_SEPARATOR : f.getName(); + } + + /** + * Sets the name of the file + * + * Does nothing if the new name is null, empty or includes "/" ; or if the file is the root directory + */ + public void setFileName(String name) { + Log_OC.d(TAG, "OCFile name changin from " + mRemotePath); + if (name != null && name.length() > 0 && !name.contains(PATH_SEPARATOR) && !mRemotePath.equals(PATH_SEPARATOR)) { + String parent = (new File(getRemotePath())).getParent(); + parent = (parent.endsWith(PATH_SEPARATOR)) ? parent : parent + PATH_SEPARATOR; + mRemotePath = parent + name; + if (isDirectory()) { + mRemotePath += PATH_SEPARATOR; + } + Log_OC.d(TAG, "OCFile name changed to " + mRemotePath); + } + } + + /** + * Can be used to get the Mimetype + * + * @return the Mimetype as a String + */ + public String getMimetype() { + return mMimeType; + } + + /** + * Adds a file to this directory. If this file is not a directory, an + * exception gets thrown. + * + * @param file to add + * @throws IllegalStateException if you try to add a something and this is + * not a directory + */ + public void addFile(OCFile file) throws IllegalStateException { + if (isDirectory()) { + file.mParentId = mId; + mNeedsUpdating = true; + return; + } + throw new IllegalStateException( + "This is not a directory where you can add stuff to!"); + } + + /** + * Used internally. Reset all file properties + */ + private void resetData() { + mId = -1; + mRemotePath = null; + mParentId = 0; + mLocalPath = null; + mMimeType = null; + mLength = 0; + mCreationTimestamp = 0; + mModifiedTimestamp = 0; + mModifiedTimestampAtLastSyncForData = 0; + mLastSyncDateForProperties = 0; + mLastSyncDateForData = 0; + mKeepInSync = false; + mNeedsUpdating = false; + } + + /** + * Sets the ID of the file + * + * @param file_id to set + */ + public void setFileId(long file_id) { + mId = file_id; + } + + /** + * Sets the Mime-Type of the + * + * @param mimetype to set + */ + public void setMimetype(String mimetype) { + mMimeType = mimetype; + } + + /** + * Sets the ID of the parent folder + * + * @param parent_id to set + */ + public void setParentId(long parent_id) { + mParentId = parent_id; + } + + /** + * Sets the file size in bytes + * + * @param file_len to set + */ + public void setFileLength(long file_len) { + mLength = file_len; + } + + /** + * Returns the size of the file in bytes + * + * @return The filesize in bytes + */ + public long getFileLength() { + return mLength; + } + + /** + * Returns the ID of the parent Folder + * + * @return The ID + */ + public long getParentId() { + return mParentId; + } + + /** + * Check, if this file needs updating + * + * @return + */ + public boolean needsUpdatingWhileSaving() { + return mNeedsUpdating; + } + + public long getLastSyncDateForProperties() { + return mLastSyncDateForProperties; + } + + public void setLastSyncDateForProperties(long lastSyncDate) { + mLastSyncDateForProperties = lastSyncDate; + } + + public long getLastSyncDateForData() { + return mLastSyncDateForData; + } + + public void setLastSyncDateForData(long lastSyncDate) { + mLastSyncDateForData = lastSyncDate; + } + + public void setKeepInSync(boolean keepInSync) { + mKeepInSync = keepInSync; + } + + public boolean keepInSync() { + return mKeepInSync; + } + + @Override + public int describeContents() { + return this.hashCode(); + } + + @Override + public int compareTo(OCFile another) { + if (isDirectory() && another.isDirectory()) { + return getRemotePath().toLowerCase().compareTo(another.getRemotePath().toLowerCase()); + } else if (isDirectory()) { + return -1; + } else if (another.isDirectory()) { + return 1; + } + return getRemotePath().toLowerCase().compareTo(another.getRemotePath().toLowerCase()); + } + + @Override + public boolean equals(Object o) { + if(o instanceof OCFile){ + OCFile that = (OCFile) o; + if(that != null){ + return this.mId == that.mId; + } + } + + return false; + } + + @Override + public String toString() { + String asString = "[id=%s, name=%s, mime=%s, downloaded=%s, local=%s, remote=%s, parentId=%s, keepInSinc=%s]"; + asString = String.format(asString, Long.valueOf(mId), getFileName(), mMimeType, isDown(), mLocalPath, mRemotePath, Long.valueOf(mParentId), Boolean.valueOf(mKeepInSync)); + return asString; + } + + public String getEtag() { + return mEtag; + } + + public long getLocalModificationTimestamp() { + if (mLocalPath != null && mLocalPath.length() > 0) { + File f = new File(mLocalPath); + return f.lastModified(); + } + return 0; + } + + /** @return 'True' if the file contains audio */ + public boolean isAudio() { + return (mMimeType != null && mMimeType.startsWith("audio/")); + } + + /** @return 'True' if the file contains video */ + public boolean isVideo() { + return (mMimeType != null && mMimeType.startsWith("video/")); + } + + /** @return 'True' if the file contains an image */ + public boolean isImage() { + return ((mMimeType != null && mMimeType.startsWith("image/")) || + getMimeTypeFromName().startsWith("image/")); + } + + public String getMimeTypeFromName() { + String extension = ""; + int pos = mRemotePath.lastIndexOf('.'); + if (pos >= 0) { + extension = mRemotePath.substring(pos + 1); + } + String result = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase()); + return (result != null) ? result : ""; + } + +} diff --git a/src/com/owncloud/android/db/DbHandler.java b/src/com/owncloud/android/db/DbHandler.java new file mode 100644 index 00000000..8f535629 --- /dev/null +++ b/src/com/owncloud/android/db/DbHandler.java @@ -0,0 +1,120 @@ +/* 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 com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; + +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 + * + * @author Bartek Przybylski + * + */ +public class DbHandler { + private SQLiteDatabase mDB; + private OpenerHelper mHelper; + private final String mDatabaseName; // = "ownCloud"; + 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) { + mHelper = new OpenerHelper(context); + mDB = mHelper.getWritableDatabase(); + mDatabaseName = MainApp.getDBName(); + } + + 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;"); + + } + } +} diff --git a/src/com/owncloud/android/db/ProviderMeta.java b/src/com/owncloud/android/db/ProviderMeta.java new file mode 100644 index 00000000..a082a84e --- /dev/null +++ b/src/com/owncloud/android/db/ProviderMeta.java @@ -0,0 +1,73 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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 com.owncloud.android.MainApp; + +import android.net.Uri; +import android.provider.BaseColumns; + +/** + * Meta-Class that holds various static field information + * + * @author Bartek Przybylski + * + */ +public class ProviderMeta { + + /* These constants are now in MainApp + public static final String AUTHORITY_FILES = "org.owncloud"; + public static final String DB_FILE = "owncloud.db"; + */ + public static final String DB_NAME = "filelist"; + public static final int DB_VERSION = 4; + + private ProviderMeta() { + } + + static public class ProviderTableMeta implements BaseColumns { + public static final String DB_NAME = "filelist"; + 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 String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.owncloud.file"; + public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.owncloud.file"; + + 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 DEFAULT_SORT_ORDER = FILE_NAME + + " collate nocase asc"; + + } +} diff --git a/src/com/owncloud/android/extensions/ExtensionsAvailableActivity.java b/src/com/owncloud/android/extensions/ExtensionsAvailableActivity.java new file mode 100644 index 00000000..7b39931c --- /dev/null +++ b/src/com/owncloud/android/extensions/ExtensionsAvailableActivity.java @@ -0,0 +1,35 @@ +/* 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.extensions; + +import android.os.Bundle; +import android.support.v4.app.FragmentManager; + +import com.actionbarsherlock.app.SherlockFragmentActivity; + +public class ExtensionsAvailableActivity extends SherlockFragmentActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + FragmentManager fm = getSupportFragmentManager(); + ExtensionsAvailableDialog ead = new ExtensionsAvailableDialog(); + ead.show(fm, "extensions_available_dialog"); + } +} diff --git a/src/com/owncloud/android/extensions/ExtensionsAvailableDialog.java b/src/com/owncloud/android/extensions/ExtensionsAvailableDialog.java new file mode 100644 index 00000000..efd4e01f --- /dev/null +++ b/src/com/owncloud/android/extensions/ExtensionsAvailableDialog.java @@ -0,0 +1,70 @@ +/* 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.extensions; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.ui.CustomButton; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.View.OnClickListener; + +public class ExtensionsAvailableDialog extends DialogFragment implements + OnClickListener { + + public ExtensionsAvailableDialog() { + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.extensions_available_dialog, + container); + CustomButton btnYes = (CustomButton) view.findViewById(R.id.buttonYes); + CustomButton btnNo = (CustomButton) view.findViewById(R.id.buttonNo); + + btnYes.setOnClickListener(this); + btnNo.setOnClickListener(this); + getDialog().setTitle(R.string.extensions_avail_title); + return view; + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.buttonYes: { + Intent i = new Intent(getActivity(), ExtensionsListActivity.class); + startActivity(i); + getActivity().finish(); + } + break; + case R.id.buttonNo: + getActivity().finish(); + break; + default: + Log_OC.e("EAD", "Button with unknown id clicked " + v.getId()); + } + } + +} diff --git a/src/com/owncloud/android/extensions/ExtensionsListActivity.java b/src/com/owncloud/android/extensions/ExtensionsListActivity.java new file mode 100644 index 00000000..02fb4a9f --- /dev/null +++ b/src/com/owncloud/android/extensions/ExtensionsListActivity.java @@ -0,0 +1,156 @@ +/* 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.extensions; + +import java.util.HashMap; +import java.util.LinkedList; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.methods.GetMethod; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.utils.OwnCloudVersion; + + + +import android.R; +import android.app.ListActivity; +import android.os.Bundle; +import android.os.Handler; +import android.widget.SimpleAdapter; + +public class ExtensionsListActivity extends ListActivity { + + private static final String packages_url = "http://alefzero.eu/a/packages.php"; + + private Thread mGetterThread; + private final Handler mHandler = new Handler(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mGetterThread = new Thread(new JsonGetter()); + mGetterThread.start(); + } + + public void done(JSONArray a) { + LinkedList> ll = new LinkedList>(); + for (int i = 0; i < a.length(); ++i) { + try { + ExtensionApplicationEntry ela = new ExtensionApplicationEntry( + ((JSONObject) a.get(i))); + HashMap ss = new HashMap(); + ss.put("NAME", ela.getName()); + ss.put("DESC", ela.getDescription()); + ll.add(ss); + } catch (JSONException e) { + e.printStackTrace(); + } + } + setListAdapter(new SimpleAdapter(this, ll, R.layout.simple_list_item_2, + new String[] { "NAME", "DESC" }, new int[] { + android.R.id.text1, android.R.id.text2 })); + + } + + private class JsonGetter implements Runnable { + + @Override + public void run() { + HttpClient hc = new HttpClient(); + GetMethod gm = new GetMethod(packages_url); + final JSONArray ar; + try { + hc.executeMethod(gm); + Log_OC.e("ASD", gm.getResponseBodyAsString() + ""); + ar = new JSONObject(gm.getResponseBodyAsString()) + .getJSONArray("apps"); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + mHandler.post(new Runnable() { + @Override + public void run() { + done(ar); + } + }); + + } + + } + + private class ExtensionApplicationEntry { + private static final String APP_NAME = "name"; + private static final String APP_VERSION = "version"; + private static final String APP_DESC = "description"; + private static final String APP_ICON = "icon"; + private static final String APP_URL = "download"; + private static final String APP_PLAYID = "play_id"; + + private String mName, mDescription, mIcon, mDownload, mPlayId; + private OwnCloudVersion mVersion; + + public ExtensionApplicationEntry(JSONObject appentry) { + try { + mName = appentry.getString(APP_NAME); + mDescription = appentry.getString(APP_DESC); + mIcon = appentry.getString(APP_ICON); + mDownload = appentry.getString(APP_URL); + mPlayId = appentry.getString(APP_PLAYID); + mVersion = new OwnCloudVersion(appentry.getString(APP_VERSION)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public String getName() { + return mName; + } + + public String getDescription() { + return mDescription; + } + + @SuppressWarnings("unused") + public String getIcon() { + return mIcon; + } + + @SuppressWarnings("unused") + public String getDownload() { + return mDownload; + } + + @SuppressWarnings("unused") + public String getPlayId() { + return mPlayId; + } + + @SuppressWarnings("unused") + public OwnCloudVersion getVersion() { + return mVersion; + } + } + +} diff --git a/src/com/owncloud/android/files/BootupBroadcastReceiver.java b/src/com/owncloud/android/files/BootupBroadcastReceiver.java new file mode 100644 index 00000000..8a8c4306 --- /dev/null +++ b/src/com/owncloud/android/files/BootupBroadcastReceiver.java @@ -0,0 +1,46 @@ +/* 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; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.files.services.FileObserverService; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +public class BootupBroadcastReceiver extends BroadcastReceiver { + + private static String TAG = "BootupBroadcastReceiver"; + + @Override + public void onReceive(Context context, Intent intent) { + if (!intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { + Log_OC.wtf(TAG, "Incorrect action sent " + intent.getAction()); + return; + } + Log_OC.d(TAG, "Starting file observer service..."); + Intent i = new Intent(context, FileObserverService.class); + i.putExtra(FileObserverService.KEY_FILE_CMD, + FileObserverService.CMD_INIT_OBSERVED_LIST); + context.startService(i); + Log_OC.d(TAG, "DONE"); + } + +} diff --git a/src/com/owncloud/android/files/FileHandler.java b/src/com/owncloud/android/files/FileHandler.java new file mode 100644 index 00000000..2eb754dd --- /dev/null +++ b/src/com/owncloud/android/files/FileHandler.java @@ -0,0 +1,30 @@ +/* ownCloud Android client application + * 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; + +import com.owncloud.android.datamodel.OCFile; + +public interface FileHandler { + + /** + * TODO + */ + public void openFile(OCFile file); + + +} diff --git a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java new file mode 100644 index 00000000..8d453f62 --- /dev/null +++ b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java @@ -0,0 +1,215 @@ +/* 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; + +import java.io.File; + +import com.owncloud.android.Log_OC; +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.utils.FileStorageUtils; + + +import android.accounts.Account; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +//import android.content.IntentFilter; +import android.database.Cursor; +import android.net.ConnectivityManager; +import android.net.NetworkInfo.State; +import android.preference.PreferenceManager; +import android.provider.MediaStore.Images.Media; +import android.webkit.MimeTypeMap; + + +public class InstantUploadBroadcastReceiver extends BroadcastReceiver { + + private static String TAG = "InstantUploadBroadcastReceiver"; + private static final String[] CONTENT_PROJECTION = { Media.DATA, Media.DISPLAY_NAME, Media.MIME_TYPE, Media.SIZE }; + //Unofficial action, works for most devices but not HTC. See: https://github.com/owncloud/android/issues/6 + private static String NEW_PHOTO_ACTION_UNOFFICIAL = "com.android.camera.NEW_PICTURE"; + //Officially supported action since SDK 14: http://developer.android.com/reference/android/hardware/Camera.html#ACTION_NEW_PICTURE + private static String NEW_PHOTO_ACTION = "android.hardware.action.NEW_PICTURE"; + + @Override + public void onReceive(Context context, Intent intent) { + Log_OC.d(TAG, "Received: " + intent.getAction()); + + FileUploader fileUploader = new FileUploader(); + + if (intent.getAction().equals(android.net.ConnectivityManager.CONNECTIVITY_ACTION)) { + handleConnectivityAction(context, intent); + }else if (intent.getAction().equals(NEW_PHOTO_ACTION_UNOFFICIAL)) { + handleNewPhotoAction(context, intent); + Log_OC.d(TAG, "UNOFFICIAL processed: com.android.camera.NEW_PICTURE"); + } else if (intent.getAction().equals(NEW_PHOTO_ACTION)) { + handleNewPhotoAction(context, intent); + Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_PICTURE"); + } else if (intent.getAction().equals(fileUploader.getUploadFinishMessage())) { + handleUploadFinished(context, intent); + } else { + Log_OC.e(TAG, "Incorrect intent sent: " + intent.getAction()); + } + } + + private void handleUploadFinished(Context context, Intent intent) { + // remove successfull uploading, ignore rest for reupload on reconnect + /* + if (intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false)) { + DbHandler db = new DbHandler(context); + String localPath = intent.getStringExtra(FileUploader.EXTRA_OLD_FILE_PATH); + if (!db.removeIUPendingFile(localPath)) { + Log_OC.w(TAG, "Tried to remove non existing instant upload file " + localPath); + } + db.close(); + } + */ + } + + private void handleNewPhotoAction(Context context, Intent intent) { + if (!instantUploadEnabled(context)) { + Log_OC.d(TAG, "Instant upload disabled, aborting uploading"); + return; + } + + Account account = AccountUtils.getCurrentOwnCloudAccount(context); + if (account == null) { + Log_OC.w(TAG, "No owncloud account found for instant upload, aborting"); + return; + } + + Cursor c = context.getContentResolver().query(intent.getData(), CONTENT_PROJECTION, null, null, null); + + if (!c.moveToFirst()) { + Log_OC.e(TAG, "Couldn't resolve given uri: " + intent.getDataString()); + return; + } + + String file_path = c.getString(c.getColumnIndex(Media.DATA)); + String file_name = c.getString(c.getColumnIndex(Media.DISPLAY_NAME)); + String mime_type = c.getString(c.getColumnIndex(Media.MIME_TYPE)); + + c.close(); + Log_OC.e(TAG, file_path + ""); + + // same always temporally the picture to upload + DbHandler db = new DbHandler(context); + db.putFileForLater(file_path, account.name, null); + db.close(); + + if (!isOnline(context) || (instantUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context))) { + return; + } + + // register for upload finishe message + // there is a litte problem with android API, we can register for + // particular + // intent in registerReceiver but we cannot unregister from precise + // intent + // we can unregister from entire listenings but thats suck a bit. + // On the other hand this might be only for dynamicly registered + // broadcast receivers, needs investigation. + /*IntentFilter filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE); + context.getApplicationContext().registerReceiver(this, filter);*/ + + 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); + context.startService(i); + + } + + private void handleConnectivityAction(Context context, Intent intent) { + if (!instantUploadEnabled(context)) { + Log_OC.d(TAG, "Instant upload disabled, abording uploading"); + return; + } + + if (!intent.hasExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY) + && isOnline(context) + && (!instantUploadViaWiFiOnly(context) || (instantUploadViaWiFiOnly(context) == isConnectedViaWiFi(context) == true))) { + DbHandler db = new DbHandler(context); + Cursor c = db.getAwaitingFiles(); + if (c.moveToFirst()) { + //IntentFilter filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE); + //context.getApplicationContext().registerReceiver(this, filter); + 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; + } + + public static boolean instantUploadEnabled(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_uploading", false); + } + + public static boolean instantUploadViaWiFiOnly(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_upload_on_wifi", false); + } +} diff --git a/src/com/owncloud/android/files/OwnCloudFileObserver.java b/src/com/owncloud/android/files/OwnCloudFileObserver.java new file mode 100644 index 00000000..8f2e3ee6 --- /dev/null +++ b/src/com/owncloud/android/files/OwnCloudFileObserver.java @@ -0,0 +1,103 @@ +/* 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; + +import java.io.File; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.operations.SynchronizeFileOperation; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.ui.activity.ConflictsResolveActivity; + + + +import android.accounts.Account; +import android.content.Context; +import android.content.Intent; +import android.os.FileObserver; + +public class OwnCloudFileObserver extends FileObserver { + + public static int CHANGES_ONLY = CLOSE_WRITE; + + private static String TAG = OwnCloudFileObserver.class.getSimpleName(); + + private String mPath; + private int mMask; + private Account mOCAccount; + //private OCFile mFile; + private Context mContext; + + + public OwnCloudFileObserver(String path, Account account, Context context, int mask) { + super(path, mask); + if (path == null) + throw new IllegalArgumentException("NULL path argument received"); + /*if (file == null) + throw new IllegalArgumentException("NULL file argument received");*/ + if (account == null) + throw new IllegalArgumentException("NULL account argument received"); + if (context == null) + throw new IllegalArgumentException("NULL context argument received"); + /*if (!path.equals(file.getStoragePath()) && !path.equals(FileStorageUtils.getDefaultSavePathFor(account.name, file))) + throw new IllegalArgumentException("File argument is not linked to the local file set in path argument"); */ + mPath = path; + //mFile = file; + mOCAccount = account; + mContext = context; + mMask = mask; + } + + @Override + public void onEvent(int event, String path) { + Log_OC.d(TAG, "Got file modified with event " + event + " and path " + mPath + ((path != null) ? File.separator + path : "")); + if ((event & mMask) == 0) { + Log_OC.wtf(TAG, "Incorrect event " + event + " sent for file " + mPath + ((path != null) ? File.separator + path : "") + + " with registered for " + mMask + " and original path " + + mPath); + return; + } + FileDataStorageManager storageManager = new FileDataStorageManager(mOCAccount, mContext.getContentResolver()); + OCFile file = storageManager.getFileByLocalPath(mPath); // a fresh object is needed; many things could have occurred to the file since it was registered to observe + // again, assuming that local files are linked to a remote file AT MOST, SOMETHING TO BE DONE; + SynchronizeFileOperation sfo = new SynchronizeFileOperation(file, + null, + storageManager, + mOCAccount, + true, + true, + mContext); + RemoteOperationResult result = sfo.execute(mOCAccount, mContext); + if (result.getCode() == ResultCode.SYNC_CONFLICT) { + // ISSUE 5: if the user is not running the app (this is a service!), 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_ACCOUNT, mOCAccount); + mContext.startActivity(i); + } + // TODO save other errors in some point where the user can inspect them later; + // or maybe just toast them; + // or nothing, very strange fails + } + +} diff --git a/src/com/owncloud/android/files/managers/OCNotificationManager.java b/src/com/owncloud/android/files/managers/OCNotificationManager.java new file mode 100644 index 00000000..11a8c377 --- /dev/null +++ b/src/com/owncloud/android/files/managers/OCNotificationManager.java @@ -0,0 +1,155 @@ +/* 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.managers; + +import java.util.HashMap; +import java.util.Map; + +import com.owncloud.android.R; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.widget.RemoteViews; + + +public class OCNotificationManager { + + enum NotificationType { + NOTIFICATION_SIMPLE, + NOTIFICATION_PROGRESS + } + + static public class NotificationData { + private String mText, mSubtitle; + private int mPercent; + private boolean mOngoing; + + public NotificationData(String text, String subtitle, boolean ongoing) { + this(text, subtitle, -1, ongoing); + } + + public NotificationData(int percent, boolean ongoing) { + this(null, null, percent, ongoing); + } + + public NotificationData(String text, int percent, boolean ongoing) { + this(text, null, percent, ongoing); + } + + public NotificationData(String text, String subtitle, int percent, boolean ongoing) { + mText = text; + mPercent = percent; + mSubtitle = subtitle; + mOngoing = ongoing; + } + + public String getText() { return mText; } + public int getPercent() { return mPercent; } + public String getSubtitle() { return mSubtitle; } + public boolean getOngoing() { return mOngoing; } + } + + static private OCNotificationManager mInstance = null; + + private class NotificationTypePair { + public Notification mNotificaiton; + public NotificationType mType; + public NotificationTypePair(Notification n, NotificationType type) { + mNotificaiton = n; + mType = type; + } + } + + private Context mContext; + private Map mNotificationMap; + private int mNotificationCounter; + NotificationManager mNM; + + static OCNotificationManager getInstance(Context context) { + if (mInstance == null) + mInstance = new OCNotificationManager(context); + return mInstance; + } + + OCNotificationManager(Context context) { + mContext = context; + mNotificationMap = new HashMap(); + mNM = (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE); + mNotificationCounter = 0; + } + + public int postNotification(NotificationType type, NotificationData data) { + mNotificationCounter++; + Notification notification = null; + + switch (type) { + case NOTIFICATION_SIMPLE: + notification = new Notification(R.drawable.icon, data.getText(), System.currentTimeMillis()); + break; + case NOTIFICATION_PROGRESS: + notification = new Notification(); + notification.contentView = new RemoteViews(mContext.getPackageName(), R.layout.progressbar_layout); + notification.contentView.setTextViewText(R.id.status_text, + data.getText()); + notification.contentView.setImageViewResource(R.id.status_icon, + R.id.icon); + notification.contentView.setProgressBar(R.id.status_progress, + 100, + data.getPercent(), + false); + break; + default: + return -1; + } + if (data.getOngoing()) { + notification.flags |= notification.flags | Notification.FLAG_ONGOING_EVENT; + } + + mNotificationMap.put(mNotificationCounter, new NotificationTypePair(notification, type)); + return mNotificationCounter; + } + + public boolean updateNotification(int notification_id, NotificationData data) { + if (!mNotificationMap.containsKey(notification_id)) { + return false; + } + NotificationTypePair pair = mNotificationMap.get(notification_id); + switch (pair.mType) { + case NOTIFICATION_PROGRESS: + pair.mNotificaiton.contentView.setProgressBar(R.id.status_text, + 100, + data.getPercent(), + false); + return true; + case NOTIFICATION_SIMPLE: + pair.mNotificaiton = new Notification(R.drawable.icon, + data.getText(), System.currentTimeMillis()); + mNM.notify(notification_id, pair.mNotificaiton); + return true; + default: + return false; + } + } + + public void discardNotification(int notification_id) { + mNM.cancel(notification_id); + mNotificationMap.remove(notification_id); + } +} diff --git a/src/com/owncloud/android/files/services/FileDownloader.java b/src/com/owncloud/android/files/services/FileDownloader.java new file mode 100644 index 00000000..64d5d3ed --- /dev/null +++ b/src/com/owncloud/android/files/services/FileDownloader.java @@ -0,0 +1,549 @@ +/* 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.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 com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AuthenticatorActivity; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.network.OwnCloudClientUtils; +import com.owncloud.android.operations.DownloadFileOperation; +import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.ui.preview.PreviewImageActivity; +import com.owncloud.android.ui.preview.PreviewImageFragment; + +import eu.alefzero.webdav.OnDatatransferProgressListener; + + +import android.accounts.Account; +import android.accounts.AccountsException; +import android.app.Notification; +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.widget.RemoteViews; + +import eu.alefzero.webdav.WebdavClient; + +public class FileDownloader extends Service implements OnDatatransferProgressListener { + + public static final String EXTRA_ACCOUNT = "ACCOUNT"; + public static final String EXTRA_FILE = "FILE"; + + private static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED"; + private static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH"; + public static final String EXTRA_DOWNLOAD_RESULT = "RESULT"; + public static final String EXTRA_FILE_PATH = "FILE_PATH"; + public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH"; + public static final String ACCOUNT_NAME = "ACCOUNT_NAME"; + + private static final String TAG = "FileDownloader"; + + private Looper mServiceLooper; + private ServiceHandler mServiceHandler; + private IBinder mBinder; + private WebdavClient mDownloadClient = null; + private Account mLastAccount = null; + private FileDataStorageManager mStorageManager; + + private ConcurrentMap mPendingDownloads = new ConcurrentHashMap(); + private DownloadFileOperation mCurrentDownload = null; + + private NotificationManager mNotificationManager; + private Notification mNotification; + private int mLastPercent; + + + public String getDownloadAddedMessage() { + return getClass().getName().toString() + DOWNLOAD_ADDED_MESSAGE; + } + + public String getDownloadFinishMessage() { + return getClass().getName().toString() + DOWNLOAD_FINISH_MESSAGE; + } + + /** + * Builds a key for mPendingDownloads from the account and file to download + * + * @param account Account where the file to download is stored + * @param file File to download + */ + private String buildRemoteName(Account account, OCFile file) { + return account.name + file.getRemotePath(); + } + + + /** + * Service initialization + */ + @Override + public void onCreate() { + super.onCreate(); + mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + HandlerThread thread = new HandlerThread("FileDownloaderThread", + Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + mServiceLooper = thread.getLooper(); + mServiceHandler = new ServiceHandler(mServiceLooper, this); + mBinder = new FileDownloaderBinder(); + } + + /** + * Entry point to add one or several files to the queue of downloads. + * + * New downloads 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) { + if ( !intent.hasExtra(EXTRA_ACCOUNT) || + !intent.hasExtra(EXTRA_FILE) + /*!intent.hasExtra(EXTRA_FILE_PATH) || + !intent.hasExtra(EXTRA_REMOTE_PATH)*/ + ) { + Log_OC.e(TAG, "Not enough information provided in intent"); + return START_NOT_STICKY; + } + Account account = intent.getParcelableExtra(EXTRA_ACCOUNT); + OCFile file = intent.getParcelableExtra(EXTRA_FILE); + + AbstractList requestedDownloads = new Vector(); // dvelasco: now this always contains just one element, but that can change in a near future (download of multiple selection) + String downloadKey = buildRemoteName(account, file); + try { + DownloadFileOperation newDownload = new DownloadFileOperation(account, file); + mPendingDownloads.putIfAbsent(downloadKey, newDownload); + newDownload.addDatatransferProgressListener(this); + newDownload.addDatatransferProgressListener((FileDownloaderBinder)mBinder); + requestedDownloads.add(downloadKey); + sendBroadcastNewDownload(newDownload); + + } catch (IllegalArgumentException e) { + Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage()); + return START_NOT_STICKY; + } + + if (requestedDownloads.size() > 0) { + Message msg = mServiceHandler.obtainMessage(); + msg.arg1 = startId; + msg.obj = requestedDownloads; + mServiceHandler.sendMessage(msg); + } + + return START_NOT_STICKY; + } + + + /** + * Provides a binder object that clients can use to perform operations on the queue of downloads, excepting the addition of new files. + * + * Implemented to perform cancellation, pause and resume of existing downloads. + */ + @Override + public IBinder onBind(Intent arg0) { + return mBinder; + } + + + /** + * Called when ALL the bound clients were onbound. + */ + @Override + public boolean onUnbind(Intent intent) { + ((FileDownloaderBinder)mBinder).clearListeners(); + return false; // not accepting rebinding (default behaviour) + } + + + /** + * Binder to let client components to perform operations on the queue of downloads. + * + * It provides by itself the available operations. + */ + public class FileDownloaderBinder extends Binder implements OnDatatransferProgressListener { + + /** + * Map of listeners that will be reported about progress of downloads from a {@link FileDownloaderBinder} instance + */ + private Map mBoundListeners = new HashMap(); + + + /** + * Cancels a pending or current download of a remote file. + * + * @param account Owncloud account where the remote file is stored. + * @param file A file in the queue of pending downloads + */ + public void cancel(Account account, OCFile file) { + DownloadFileOperation download = null; + synchronized (mPendingDownloads) { + download = mPendingDownloads.remove(buildRemoteName(account, file)); + } + if (download != null) { + download.cancel(); + } + } + + + public void clearListeners() { + mBoundListeners.clear(); + } + + + /** + * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download. + * + * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download. + * + * @param account Owncloud account where the remote file is stored. + * @param file A file that could be in the queue of downloads. + */ + public boolean isDownloading(Account account, OCFile file) { + if (account == null || file == null) return false; + String targetKey = buildRemoteName(account, file); + synchronized (mPendingDownloads) { + if (file.isDirectory()) { + // this can be slow if there are many downloads :( + Iterator it = mPendingDownloads.keySet().iterator(); + boolean found = false; + while (it.hasNext() && !found) { + found = it.next().startsWith(targetKey); + } + return found; + } else { + return (mPendingDownloads.containsKey(targetKey)); + } + } + } + + + /** + * Adds a listener interested in the progress of the download 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 download 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) { + // old way, should not be in use any more + } + + + @Override + public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, + String fileName) { + String key = buildRemoteName(mCurrentDownload.getAccount(), mCurrentDownload.getFile()); + OnDatatransferProgressListener boundListener = mBoundListeners.get(key); + if (boundListener != null) { + boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName); + } + } + + } + + + /** + * Download worker. Performs the pending downloads 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 + FileDownloader mService; + public ServiceHandler(Looper looper, FileDownloader 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 requestedDownloads = (AbstractList) msg.obj; + if (msg.obj != null) { + Iterator it = requestedDownloads.iterator(); + while (it.hasNext()) { + mService.downloadFile(it.next()); + } + } + mService.stopSelf(msg.arg1); + } + } + + + /** + * Core download method: requests a file to download and stores it. + * + * @param downloadKey Key to access the download to perform, contained in mPendingDownloads + */ + private void downloadFile(String downloadKey) { + + synchronized(mPendingDownloads) { + mCurrentDownload = mPendingDownloads.get(downloadKey); + } + + if (mCurrentDownload != null) { + + notifyDownloadStart(mCurrentDownload); + + RemoteOperationResult downloadResult = null; + try { + /// prepare client object to send the request to the ownCloud server + if (mDownloadClient == null || !mLastAccount.equals(mCurrentDownload.getAccount())) { + mLastAccount = mCurrentDownload.getAccount(); + mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver()); + mDownloadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext()); + } + + /// perform the download + downloadResult = mCurrentDownload.execute(mDownloadClient); + if (downloadResult.isSuccess()) { + saveDownloadedFile(); + } + + } catch (AccountsException e) { + Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e); + downloadResult = new RemoteOperationResult(e); + } catch (IOException e) { + Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e); + downloadResult = new RemoteOperationResult(e); + + } finally { + synchronized(mPendingDownloads) { + mPendingDownloads.remove(downloadKey); + } + } + + + /// notify result + notifyDownloadResult(mCurrentDownload, downloadResult); + + sendBroadcastDownloadFinished(mCurrentDownload, downloadResult); + } + } + + + /** + * Updates the OC File after a successful download. + */ + private void saveDownloadedFile() { + OCFile file = mCurrentDownload.getFile(); + long syncDate = System.currentTimeMillis(); + file.setLastSyncDateForProperties(syncDate); + file.setLastSyncDateForData(syncDate); + file.setModificationTimestamp(mCurrentDownload.getModificationTimestamp()); + file.setModificationTimestampAtLastSyncForData(mCurrentDownload.getModificationTimestamp()); + // file.setEtag(mCurrentDownload.getEtag()); // TODO Etag, where available + file.setMimetype(mCurrentDownload.getMimeType()); + file.setStoragePath(mCurrentDownload.getSavePath()); + file.setFileLength((new File(mCurrentDownload.getSavePath()).length())); + mStorageManager.saveFile(file); + } + + + /** + * Creates a status notification to show the download progress + * + * @param download Download operation starting. + */ + private void notifyDownloadStart(DownloadFileOperation download) { + /// create status notification with a progress bar + mLastPercent = 0; + mNotification = new Notification(R.drawable.icon, getString(R.string.downloader_download_in_progress_ticker), System.currentTimeMillis()); + mNotification.flags |= Notification.FLAG_ONGOING_EVENT; + mNotification.contentView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.progressbar_layout); + mNotification.contentView.setProgressBar(R.id.status_progress, 100, 0, download.getSize() < 0); + mNotification.contentView.setTextViewText(R.id.status_text, String.format(getString(R.string.downloader_download_in_progress_content), 0, new File(download.getSavePath()).getName())); + mNotification.contentView.setImageViewResource(R.id.status_icon, R.drawable.icon); + + /// includes a pending intent in the notification showing the details view of the file + Intent showDetailsIntent = null; + if (PreviewImageFragment.canBePreviewed(download.getFile())) { + showDetailsIntent = new Intent(this, PreviewImageActivity.class); + } else { + showDetailsIntent = new Intent(this, FileDisplayActivity.class); + } + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, download.getFile()); + showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, download.getAccount()); + showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), showDetailsIntent, 0); + + mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotification); + } + + + /** + * Callback method to update the progress bar in the status notification. + */ + @Override + public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String fileName) { + int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer)); + if (percent != mLastPercent) { + mNotification.contentView.setProgressBar(R.id.status_progress, 100, percent, totalToTransfer < 0); + String text = String.format(getString(R.string.downloader_download_in_progress_content), percent, fileName); + mNotification.contentView.setTextViewText(R.id.status_text, text); + mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotification); + } + mLastPercent = percent; + } + + + /** + * Callback method to update the progress bar in the status notification (old version) + */ + @Override + public void onTransferProgress(long progressRate) { + // NOTHING TO DO HERE ANYMORE + } + + + /** + * Updates the status notification with the result of a download operation. + * + * @param downloadResult Result of the download operation. + * @param download Finished download operation + */ + private void notifyDownloadResult(DownloadFileOperation download, RemoteOperationResult downloadResult) { + mNotificationManager.cancel(R.string.downloader_download_in_progress_ticker); + if (!downloadResult.isCancelled()) { + int tickerId = (downloadResult.isSuccess()) ? R.string.downloader_download_succeeded_ticker : R.string.downloader_download_failed_ticker; + int contentId = (downloadResult.isSuccess()) ? R.string.downloader_download_succeeded_content : R.string.downloader_download_failed_content; + Notification finalNotification = new Notification(R.drawable.icon, getString(tickerId), System.currentTimeMillis()); + finalNotification.flags |= Notification.FLAG_AUTO_CANCEL; + boolean needsToUpdateCredentials = (downloadResult.getCode() == ResultCode.UNAUTHORIZED || + // (downloadResult.isTemporalRedirection() && downloadResult.isIdPRedirection() + (downloadResult.isIdPRedirection() + && MainApp.getAuthTokenTypeSamlSessionCookie().equals(mDownloadClient.getAuthTokenType()))); + if (needsToUpdateCredentials) { + // let the user update credentials with one click + Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class); + updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, download.getAccount()); + updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ENFORCED_UPDATE, true); + updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_TOKEN); + updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND); + finalNotification.contentIntent = PendingIntent.getActivity(this, (int)System.currentTimeMillis(), updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT); + finalNotification.setLatestEventInfo( getApplicationContext(), + getString(tickerId), + String.format(getString(contentId), new File(download.getSavePath()).getName()), + finalNotification.contentIntent); + mDownloadClient = null; // grant that future retries on the same account will get the fresh credentials + + } else { + Intent showDetailsIntent = null; + if (downloadResult.isSuccess()) { + if (PreviewImageFragment.canBePreviewed(download.getFile())) { + showDetailsIntent = new Intent(this, PreviewImageActivity.class); + } else { + showDetailsIntent = new Intent(this, FileDisplayActivity.class); + } + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, download.getFile()); + showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, download.getAccount()); + showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + + } else { + // TODO put something smart in showDetailsIntent + showDetailsIntent = new Intent(); + } + finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), showDetailsIntent, 0); + finalNotification.setLatestEventInfo(getApplicationContext(), getString(tickerId), String.format(getString(contentId), new File(download.getSavePath()).getName()), finalNotification.contentIntent); + } + mNotificationManager.notify(tickerId, finalNotification); + } + } + + + /** + * Sends a broadcast when a download finishes in order to the interested activities can update their view + * + * @param download Finished download operation + * @param downloadResult Result of the download operation + */ + private void sendBroadcastDownloadFinished(DownloadFileOperation download, RemoteOperationResult downloadResult) { + Intent end = new Intent(getDownloadFinishMessage()); + end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess()); + end.putExtra(ACCOUNT_NAME, download.getAccount().name); + end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath()); + end.putExtra(EXTRA_FILE_PATH, download.getSavePath()); + sendStickyBroadcast(end); + } + + + /** + * Sends a broadcast when a new download is added to the queue. + * + * @param download Added download operation + */ + private void sendBroadcastNewDownload(DownloadFileOperation download) { + Intent added = new Intent(getDownloadAddedMessage()); + added.putExtra(ACCOUNT_NAME, download.getAccount().name); + added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath()); + added.putExtra(EXTRA_FILE_PATH, download.getSavePath()); + sendStickyBroadcast(added); + } + +} diff --git a/src/com/owncloud/android/files/services/FileObserverService.java b/src/com/owncloud/android/files/services/FileObserverService.java new file mode 100644 index 00000000..a0003bec --- /dev/null +++ b/src/com/owncloud/android/files/services/FileObserverService.java @@ -0,0 +1,285 @@ +/* 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.util.HashMap; +import java.util.Map; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; +import com.owncloud.android.files.OwnCloudFileObserver; +import com.owncloud.android.operations.SynchronizeFileOperation; +import com.owncloud.android.utils.FileStorageUtils; + + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.Cursor; +import android.os.Binder; +import android.os.IBinder; + +public class FileObserverService extends Service { + + public final static int CMD_INIT_OBSERVED_LIST = 1; + public final static int CMD_ADD_OBSERVED_FILE = 2; + public final static int CMD_DEL_OBSERVED_FILE = 3; + + public final static String KEY_FILE_CMD = "KEY_FILE_CMD"; + public final static String KEY_CMD_ARG_FILE = "KEY_CMD_ARG_FILE"; + public final static String KEY_CMD_ARG_ACCOUNT = "KEY_CMD_ARG_ACCOUNT"; + + private static String TAG = FileObserverService.class.getSimpleName(); + + private static Map mObserversMap; + private static DownloadCompletedReceiverBis mDownloadReceiver; + private IBinder mBinder = new LocalBinder(); + + private String mDownloadAddedMessage; + private String mDownloadFinishMessage; + + public class LocalBinder extends Binder { + FileObserverService getService() { + return FileObserverService.this; + } + } + + @Override + public void onCreate() { + super.onCreate(); + mDownloadReceiver = new DownloadCompletedReceiverBis(); + + FileDownloader downloader = new FileDownloader(); + mDownloadAddedMessage = downloader.getDownloadAddedMessage(); + mDownloadFinishMessage= downloader.getDownloadFinishMessage(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(mDownloadAddedMessage); + filter.addAction(mDownloadFinishMessage); + registerReceiver(mDownloadReceiver, filter); + + mObserversMap = new HashMap(); + //initializeObservedList(); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + unregisterReceiver(mDownloadReceiver); + mObserversMap = null; // TODO study carefully the life cycle of Services to grant the best possible observance + Log_OC.d(TAG, "Bye, bye"); + } + + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // this occurs when system tries to restart + // service, so we need to reinitialize observers + if (intent == null) { + initializeObservedList(); + return Service.START_STICKY; + } + + if (!intent.hasExtra(KEY_FILE_CMD)) { + Log_OC.e(TAG, "No KEY_FILE_CMD argument given"); + return Service.START_STICKY; + } + + switch (intent.getIntExtra(KEY_FILE_CMD, -1)) { + case CMD_INIT_OBSERVED_LIST: + initializeObservedList(); + break; + case CMD_ADD_OBSERVED_FILE: + addObservedFile( (OCFile)intent.getParcelableExtra(KEY_CMD_ARG_FILE), + (Account)intent.getParcelableExtra(KEY_CMD_ARG_ACCOUNT)); + break; + case CMD_DEL_OBSERVED_FILE: + removeObservedFile( (OCFile)intent.getParcelableExtra(KEY_CMD_ARG_FILE), + (Account)intent.getParcelableExtra(KEY_CMD_ARG_ACCOUNT)); + break; + default: + Log_OC.wtf(TAG, "Incorrect key given"); + } + + return Service.START_STICKY; + } + + + /** + * Read from the local database the list of files that must to be kept synchronized and + * starts file observers to monitor local changes on them + */ + private void initializeObservedList() { + mObserversMap.clear(); + Cursor c = getContentResolver().query( + ProviderTableMeta.CONTENT_URI, + null, + ProviderTableMeta.FILE_KEEP_IN_SYNC + " = ?", + new String[] {String.valueOf(1)}, + null); + if (c == null || !c.moveToFirst()) return; + AccountManager acm = AccountManager.get(this); + Account[] accounts = acm.getAccounts(); + do { + Account account = null; + for (Account a : accounts) + if (a.name.equals(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_ACCOUNT_OWNER)))) { + account = a; + break; + } + + if (account == null) continue; + FileDataStorageManager storage = + new FileDataStorageManager(account, getContentResolver()); + if (!storage.fileExists(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH)))) + continue; + + String path = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH)); + if (path == null || path.length() <= 0) + continue; + OwnCloudFileObserver observer = + new OwnCloudFileObserver( path, + account, + getApplicationContext(), + OwnCloudFileObserver.CHANGES_ONLY); + mObserversMap.put(path, observer); + if (new File(path).exists()) { + observer.startWatching(); + Log_OC.d(TAG, "Started watching file " + path); + } + + } while (c.moveToNext()); + c.close(); + } + + + /** + * Registers the local copy of a remote file to be observed for local changes, + * an automatically updated in the ownCloud server. + * + * This method does NOT perform a {@link SynchronizeFileOperation} over the file. + * + * TODO We are ignoring that, currently, a local file can be linked to different files + * in ownCloud if it's uploaded several times. That's something pending to update: we + * will avoid that the same local file is linked to different remote files. + * + * @param file Object representing a remote file which local copy must be observed. + * @param account OwnCloud account containing file. + */ + private void addObservedFile(OCFile file, Account account) { + if (file == null) { + Log_OC.e(TAG, "Trying to add a NULL file to observer"); + return; + } + String localPath = file.getStoragePath(); + if (localPath == null || localPath.length() <= 0) { // file downloading / to be download for the first time + localPath = FileStorageUtils.getDefaultSavePathFor(account.name, file); + } + OwnCloudFileObserver observer = mObserversMap.get(localPath); + if (observer == null) { + /// the local file was never registered to observe before + observer = new OwnCloudFileObserver( localPath, + account, + getApplicationContext(), + OwnCloudFileObserver.CHANGES_ONLY); + mObserversMap.put(localPath, observer); + Log_OC.d(TAG, "Observer added for path " + localPath); + + if (file.isDown()) { + observer.startWatching(); + Log_OC.d(TAG, "Started watching " + localPath); + } // else - the observance can't be started on a file not already down; mDownloadReceiver will get noticed when the download of the file finishes + } + + } + + + /** + * Unregisters the local copy of a remote file to be observed for local changes. + * + * Starts to watch it, if the file has a local copy to watch. + * + * TODO We are ignoring that, currently, a local file can be linked to different files + * in ownCloud if it's uploaded several times. That's something pending to update: we + * will avoid that the same local file is linked to different remote files. + * + * @param file Object representing a remote file which local copy must be not observed longer. + * @param account OwnCloud account containing file. + */ + private void removeObservedFile(OCFile file, Account account) { + if (file == null) { + Log_OC.e(TAG, "Trying to remove a NULL file"); + return; + } + String localPath = file.getStoragePath(); + if (localPath == null || localPath.length() <= 0) { + localPath = FileStorageUtils.getDefaultSavePathFor(account.name, file); + } + + OwnCloudFileObserver observer = mObserversMap.get(localPath); + if (observer != null) { + observer.stopWatching(); + mObserversMap.remove(observer); + Log_OC.d(TAG, "Stopped watching " + localPath); + } + + } + + + /** + * Private receiver listening to events broadcast by the FileDownloader service. + * + * Starts and stops the observance on registered files when they are being download, + * in order to avoid to start unnecessary synchronizations. + */ + private class DownloadCompletedReceiverBis extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + String downloadPath = intent.getStringExtra(FileDownloader.EXTRA_FILE_PATH); + OwnCloudFileObserver observer = mObserversMap.get(downloadPath); + if (observer != null) { + if (intent.getAction().equals(mDownloadFinishMessage) && + new File(downloadPath).exists()) { // the download could be successful. not; in both cases, the file could be down, due to a former download or upload + observer.startWatching(); + Log_OC.d(TAG, "Watching again " + downloadPath); + + } else if (intent.getAction().equals(mDownloadAddedMessage)) { + observer.stopWatching(); + Log_OC.d(TAG, "Disabling observance of " + downloadPath); + } + } + } + + } + +} diff --git a/src/com/owncloud/android/files/services/FileUploader.java b/src/com/owncloud/android/files/services/FileUploader.java new file mode 100644 index 00000000..11084623 --- /dev/null +++ b/src/com/owncloud/android/files/services/FileUploader.java @@ -0,0 +1,924 @@ +/* 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.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 org.apache.http.HttpStatus; +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.MultiStatus; +import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountAuthenticator; +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.network.OwnCloudClientUtils; +import com.owncloud.android.operations.ChunkedUploadFileOperation; +import com.owncloud.android.operations.CreateFolderOperation; +import com.owncloud.android.operations.ExistenceCheckOperation; +import com.owncloud.android.operations.RemoteOperation; +import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.operations.UploadFileOperation; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.ui.activity.FailedUploadActivity; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.ui.activity.InstantUploadActivity; +import com.owncloud.android.ui.preview.PreviewImageActivity; +import com.owncloud.android.ui.preview.PreviewImageFragment; +import com.owncloud.android.utils.OwnCloudVersion; + + +import eu.alefzero.webdav.OnDatatransferProgressListener; +import eu.alefzero.webdav.WebdavEntry; +import eu.alefzero.webdav.WebdavUtils; + + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountsException; +import android.app.Notification; +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.webkit.MimeTypeMap; +import android.widget.RemoteViews; + + +import eu.alefzero.webdav.WebdavClient; + +public class FileUploader extends Service implements OnDatatransferProgressListener { + + 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 WebdavClient mUploadClient = null; + private Account mLastAccount = null; + private FileDataStorageManager mStorageManager; + + private ConcurrentMap mPendingUploads = new ConcurrentHashMap(); + private UploadFileOperation mCurrentUpload = null; + + private NotificationManager mNotificationManager; + private Notification mNotification; + private int mLastPercent; + private RemoteViews mDefaultNotificationContentView; + + + public String getUploadFinishMessage() { + return getClass().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; + } + + /** + * 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.i(TAG, "mPendingUploads size:" + mPendingUploads.size()); + 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(); + } + + /** + * 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) { + 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); + + 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] + : (String) null), storageManager); + if (files[i] == null) { + // TODO @andomaex add failure Notiification + return Service.START_NOT_STICKY; + } + } + } + + OwnCloudVersion ocv = new OwnCloudVersion(AccountManager.get(this).getUserData(account, + AccountAuthenticator.KEY_OC_VERSION)); + 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()); + if (chunked) { + newUpload = new ChunkedUploadFileOperation(account, files[i], isInstant, forceOverwrite, + localAction); + } else { + newUpload = new UploadFileOperation(account, files[i], isInstant, forceOverwrite, localAction); + } + if (isInstant) { + newUpload.setRemoteFolderToBeCreated(); + } + mPendingUploads.putIfAbsent(uploadKey, newUpload); // Grants that the file only upload once time + + 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) + } + + + /** + * 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 = null; + synchronized (mPendingUploads) { + upload = mPendingUploads.remove(buildRemoteName(account, file)); + } + if (upload != null) { + upload.cancel(); + } + } + + + + 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.isDirectory()) { + // 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) { + // old way, should not be in use any more + } + + + @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); + } + } + + } + + /** + * 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()); + } + } + 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) { + + 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()); + mUploadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext()); + } + + /// 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.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); + + } + + } + + /** + * 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 ExistenceCheckOperation(pathToGrant, this, false); + RemoteOperationResult result = operation.execute(mUploadClient); + if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND && mCurrentUpload.isRemoteFolderToBeCreated()) { + operation = new CreateFolderOperation( pathToGrant, + true, + mStorageManager ); + result = operation.execute(mUploadClient); + } + 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(); + long syncDate = System.currentTimeMillis(); + file.setLastSyncDateForData(syncDate); + + // / new PROPFIND to keep data consistent with server in theory, should + // return the same we already have + PropFindMethod propfind = null; + RemoteOperationResult result = null; + try { + propfind = new PropFindMethod(mUploadClient.getBaseUri() + WebdavUtils.encodePath(mCurrentUpload.getRemotePath()), + DavConstants.PROPFIND_ALL_PROP, + DavConstants.DEPTH_0); + int status = mUploadClient.executeMethod(propfind); + boolean isMultiStatus = (status == HttpStatus.SC_MULTI_STATUS); + if (isMultiStatus) { + MultiStatus resp = propfind.getResponseBodyAsMultiStatus(); + WebdavEntry we = new WebdavEntry(resp.getResponses()[0], mUploadClient.getBaseUri().getPath()); + updateOCFile(file, we); + file.setLastSyncDateForProperties(syncDate); + + } else { + mUploadClient.exhaustResponse(propfind.getResponseBodyAsStream()); + } + + result = new RemoteOperationResult(isMultiStatus, status, propfind.getResponseHeaders()); + Log_OC.i(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": " + + result.getLogMessage()); + + } catch (Exception e) { + result = new RemoteOperationResult(e); + Log_OC.e(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": " + + result.getLogMessage(), e); + + } finally { + if (propfind != null) + propfind.releaseConnection(); + } + + // / 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() + } + + mStorageManager.saveFile(file); + } + + private void updateOCFile(OCFile file, WebdavEntry we) { + file.setCreationTimestamp(we.createTimestamp()); + file.setFileLength(we.contentLength()); + file.setMimetype(we.contentType()); + file.setModificationTimestamp(we.modifiedTimestamp()); + file.setModificationTimestampAtLastSyncForData(we.modifiedTimestamp()); + // file.setEtag(mCurrentUpload.getEtag()); // TODO Etag, where available + } + + private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, + FileDataStorageManager storageManager) { + 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 + + // 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"; + } + newFile.setMimetype(mimeType); + + return newFile; + } + + /** + * Creates a status notification to show the upload progress + * + * @param upload Upload operation starting. + */ + @SuppressWarnings("deprecation") + private void notifyUploadStart(UploadFileOperation upload) { + // / create status notification with a progress bar + mLastPercent = 0; + mNotification = new Notification(R.drawable.icon, getString(R.string.uploader_upload_in_progress_ticker), + System.currentTimeMillis()); + mNotification.flags |= Notification.FLAG_ONGOING_EVENT; + mDefaultNotificationContentView = mNotification.contentView; + mNotification.contentView = new RemoteViews(getApplicationContext().getPackageName(), + R.layout.progressbar_layout); + mNotification.contentView.setProgressBar(R.id.status_progress, 100, 0, false); + mNotification.contentView.setTextViewText(R.id.status_text, + String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName())); + mNotification.contentView.setImageViewResource(R.id.status_icon, R.drawable.icon); + + /// 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); + mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), + (int) System.currentTimeMillis(), showDetailsIntent, 0); + + mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification); + } + + /** + * Callback method to update the progress bar in the status notification + */ + @Override + public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String fileName) { + int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer)); + if (percent != mLastPercent) { + mNotification.contentView.setProgressBar(R.id.status_progress, 100, percent, false); + String text = String.format(getString(R.string.uploader_upload_in_progress_content), percent, fileName); + mNotification.contentView.setTextViewText(R.id.status_text, text); + mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification); + } + mLastPercent = percent; + } + + /** + * Callback method to update the progress bar in the status notification + * (old version) + */ + @Override + public void onTransferProgress(long progressRate) { + // NOTHING TO DO HERE ANYMORE + } + + /** + * 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()); + if (uploadResult.isCancelled()) { + // / cancelled operation -> silent removal of progress notification + mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker); + + } else if (uploadResult.isSuccess()) { + // / success -> silent update of progress notification to success + // message + mNotification.flags ^= Notification.FLAG_ONGOING_EVENT; // remove + // the + // ongoing + // flag + mNotification.flags |= Notification.FLAG_AUTO_CANCEL; + mNotification.contentView = mDefaultNotificationContentView; + + /// includes a pending intent in the notification showing the details view of the file + Intent showDetailsIntent = null; + if (PreviewImageFragment.canBePreviewed(upload.getFile())) { + showDetailsIntent = new Intent(this, PreviewImageActivity.class); + } else { + 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); + mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), + (int) System.currentTimeMillis(), showDetailsIntent, 0); + + mNotification.setLatestEventInfo(getApplicationContext(), + getString(R.string.uploader_upload_succeeded_ticker), + String.format(getString(R.string.uploader_upload_succeeded_content_single), upload.getFileName()), + mNotification.contentIntent); + + mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification); // NOT + // AN + DbHandler db = new DbHandler(this.getBaseContext()); + db.removeIUPendingFile(mCurrentUpload.getOriginalStoragePath()); + db.close(); + + } else { + + // / fail -> explicit failure notification + mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker); + Notification finalNotification = new Notification(R.drawable.icon, + getString(R.string.uploader_upload_failed_ticker), System.currentTimeMillis()); + finalNotification.flags |= Notification.FLAG_AUTO_CANCEL; + String content = null; + + boolean needsToUpdateCredentials = (uploadResult.getCode() == ResultCode.UNAUTHORIZED || + //(uploadResult.isTemporalRedirection() && uploadResult.isIdPRedirection() && + (uploadResult.isIdPRedirection() && + MainApp.getAuthTokenTypeSamlSessionCookie().equals(mUploadClient.getAuthTokenType()))); + 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_ENFORCED_UPDATE, true); + updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_TOKEN); + updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND); + finalNotification.contentIntent = PendingIntent.getActivity(this, (int)System.currentTimeMillis(), updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT); + content = String.format(getString(R.string.uploader_upload_failed_content_single), upload.getFileName()); + finalNotification.setLatestEventInfo(getApplicationContext(), + getString(R.string.uploader_upload_failed_ticker), content, finalNotification.contentIntent); + mUploadClient = null; // grant that future retries on the same account will get the fresh credentials + } else { + // TODO put something smart in the contentIntent below + // finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); + //} + + if (uploadResult.getCode() == ResultCode.LOCAL_STORAGE_FULL + || uploadResult.getCode() == ResultCode.LOCAL_STORAGE_NOT_COPIED) { + // TODO we need a class to provide error messages for the users + // from a RemoteOperationResult and a RemoteOperation + content = String.format(getString(R.string.error__upload__local_file_not_copied), upload.getFileName(), + getString(R.string.app_name)); + } else if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { + content = getString(R.string.failed_upload_quota_exceeded_text); + } else { + content = String + .format(getString(R.string.uploader_upload_failed_content_single), upload.getFileName()); + } + + // we add only for instant-uploads the InstantUploadActivity and the + // db entry + Intent detailUploadIntent = null; + if (upload.isInstant() && InstantUploadActivity.IS_ENABLED) { + detailUploadIntent = new Intent(this, InstantUploadActivity.class); + detailUploadIntent.putExtra(FileUploader.KEY_ACCOUNT, upload.getAccount()); + } else { + detailUploadIntent = new Intent(this, FailedUploadActivity.class); + detailUploadIntent.putExtra(FailedUploadActivity.MESSAGE, content); + } + finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), + (int) System.currentTimeMillis(), detailUploadIntent, PendingIntent.FLAG_UPDATE_CURRENT + | PendingIntent.FLAG_ONE_SHOT); + + 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(); + } + } + } + } + finalNotification.setLatestEventInfo(getApplicationContext(), + getString(R.string.uploader_upload_failed_ticker), content, finalNotification.contentIntent); + + mNotificationManager.notify(R.string.uploader_upload_failed_ticker, finalNotification); + } + + } + + /** + * 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); + } + +} diff --git a/src/com/owncloud/android/files/services/OnUploadCompletedListener.java b/src/com/owncloud/android/files/services/OnUploadCompletedListener.java new file mode 100644 index 00000000..b6ee1aca --- /dev/null +++ b/src/com/owncloud/android/files/services/OnUploadCompletedListener.java @@ -0,0 +1,26 @@ +/* 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; + +public interface OnUploadCompletedListener extends Runnable { + + public boolean getUploadResult(); + + public void setUploadResult(boolean result); +} diff --git a/src/com/owncloud/android/location/LocationServiceLauncherReciever.java b/src/com/owncloud/android/location/LocationServiceLauncherReciever.java new file mode 100644 index 00000000..a974c565 --- /dev/null +++ b/src/com/owncloud/android/location/LocationServiceLauncherReciever.java @@ -0,0 +1,88 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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.location; + +import com.owncloud.android.Log_OC; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningServiceInfo; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +public class LocationServiceLauncherReciever extends BroadcastReceiver { + + private final String TAG = getClass().getSimpleName(); + + @Override + public void onReceive(Context context, Intent intent) { + Intent deviceTrackingIntent = new Intent(); + deviceTrackingIntent + .setAction("com.owncloud.android.location.LocationUpdateService"); + SharedPreferences preferences = PreferenceManager + .getDefaultSharedPreferences(context); + boolean trackDevice = preferences.getBoolean("enable_devicetracking", + true); + + // Used in Preferences activity so that tracking is disabled or + // reenabled + if (intent.hasExtra("TRACKING_SETTING")) { + trackDevice = intent.getBooleanExtra("TRACKING_SETTING", true); + } + + startOrStopDeviceTracking(context, trackDevice); + } + + /** + * Used internally. Starts or stops the device tracking service + * + * @param trackDevice true to start the service, false to stop it + */ + private void startOrStopDeviceTracking(Context context, boolean trackDevice) { + Intent deviceTrackingIntent = new Intent(); + deviceTrackingIntent + .setAction("com.owncloud.android.location.LocationUpdateService"); + if (!isDeviceTrackingServiceRunning(context) && trackDevice) { + Log_OC.d(TAG, "Starting device tracker service"); + context.startService(deviceTrackingIntent); + } else if (isDeviceTrackingServiceRunning(context) && !trackDevice) { + Log_OC.d(TAG, "Stopping device tracker service"); + context.stopService(deviceTrackingIntent); + } + } + + /** + * Checks to see whether or not the LocationUpdateService is running + * + * @return true, if it is. Otherwise false + */ + private boolean isDeviceTrackingServiceRunning(Context context) { + ActivityManager manager = (ActivityManager) context + .getSystemService(Context.ACTIVITY_SERVICE); + for (RunningServiceInfo service : manager + .getRunningServices(Integer.MAX_VALUE)) { + if (getClass().getName().equals(service.service.getClassName())) { + return true; + } + } + return false; + } + +} diff --git a/src/com/owncloud/android/location/LocationUpdateService.java b/src/com/owncloud/android/location/LocationUpdateService.java new file mode 100644 index 00000000..e1f529b5 --- /dev/null +++ b/src/com/owncloud/android/location/LocationUpdateService.java @@ -0,0 +1,112 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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.location; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; + +import android.app.IntentService; +import android.content.Intent; +import android.content.SharedPreferences; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.location.LocationProvider; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.widget.Toast; + + +public class LocationUpdateService extends IntentService implements + LocationListener { + + public static final String TAG = "LocationUpdateService"; + + private LocationManager mLocationManager; + private LocationProvider mLocationProvider; + private SharedPreferences mPreferences; + + public LocationUpdateService() { + super(TAG); + } + + @Override + protected void onHandleIntent(Intent intent) { + mLocationManager = (LocationManager) getSystemService(LOCATION_SERVICE); + // Determine, how we can track the device + Criteria criteria = new Criteria(); + criteria.setAccuracy(Criteria.ACCURACY_FINE); + criteria.setPowerRequirement(Criteria.POWER_LOW); + mLocationProvider = mLocationManager.getProvider(mLocationManager + .getBestProvider(criteria, true)); + + // Notify user if there is no way to track the device + if (mLocationProvider == null) { + String message = String.format(getString(R.string.location_no_provider), getString(R.string.app_name)); + Toast.makeText(this, + message, + Toast.LENGTH_LONG).show(); + stopSelf(); + return; + } + + // Get preferences for device tracking + mPreferences = PreferenceManager.getDefaultSharedPreferences(this); + boolean trackDevice = mPreferences.getBoolean("enable_devicetracking", + true); + int updateIntervall = Integer.parseInt(mPreferences.getString( + "devicetracking_update_intervall", "30")) * 60 * 1000; + int distanceBetweenLocationChecks = 50; + + // If we do shall track the device -> Stop + if (!trackDevice) { + Log_OC.d(TAG, "Devicetracking is disabled"); + stopSelf(); + return; + } + + mLocationManager.requestLocationUpdates(mLocationProvider.getName(), + updateIntervall, distanceBetweenLocationChecks, this); + } + + @Override + public void onLocationChanged(Location location) { + Log_OC.d(TAG, "Location changed: " + location); + + } + + @Override + public void onProviderDisabled(String arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void onProviderEnabled(String arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void onStatusChanged(String arg0, int arg1, Bundle arg2) { + // TODO Auto-generated method stub + + } + +} diff --git a/src/com/owncloud/android/media/MediaControlView.java b/src/com/owncloud/android/media/MediaControlView.java new file mode 100644 index 00000000..b257bd37 --- /dev/null +++ b/src/com/owncloud/android/media/MediaControlView.java @@ -0,0 +1,473 @@ +/* ownCloud Android client application + * + * 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.media; + +import android.content.Context; +import android.media.MediaPlayer; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.MediaController.MediaPlayerControl; +import android.widget.ProgressBar; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; + +import java.util.Formatter; +import java.util.Locale; + +import com.owncloud.android.R; + + +/** + * View containing controls for a {@link MediaPlayer}. + * + * Holds buttons "play / pause", "rewind", "fast forward" + * and a progress slider. + * + * It synchronizes itself with the state of the + * {@link MediaPlayer}. + * + * @author David A. Velasco + */ + +public class MediaControlView extends FrameLayout /* implements OnLayoutChangeListener, OnTouchListener */ implements OnClickListener, OnSeekBarChangeListener { + + private MediaPlayerControl mPlayer; + private Context mContext; + private View mRoot; + private ProgressBar mProgress; + private TextView mEndTime, mCurrentTime; + private boolean mDragging; + private static final int SHOW_PROGRESS = 1; + StringBuilder mFormatBuilder; + Formatter mFormatter; + private ImageButton mPauseButton; + private ImageButton mFfwdButton; + private ImageButton mRewButton; + + public MediaControlView(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + + FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ); + LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mRoot = inflate.inflate(R.layout.media_control, null); + initControllerView(mRoot); + addView(mRoot, frameParams); + + setFocusable(true); + setFocusableInTouchMode(true); + setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); + requestFocus(); + } + + @Override + public void onFinishInflate() { + /* + if (mRoot != null) + initControllerView(mRoot); + */ + } + + /* TODO REMOVE + public MediaControlView(Context context, boolean useFastForward) { + super(context); + mContext = context; + mUseFastForward = useFastForward; + initFloatingWindowLayout(); + //initFloatingWindow(); + } + */ + + /* TODO REMOVE + public MediaControlView(Context context) { + this(context, true); + } + */ + + /* T + private void initFloatingWindow() { + mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); + mWindow = PolicyManager.makeNewWindow(mContext); + mWindow.setWindowManager(mWindowManager, null, null); + mWindow.requestFeature(Window.FEATURE_NO_TITLE); + mDecor = mWindow.getDecorView(); + mDecor.setOnTouchListener(mTouchListener); + mWindow.setContentView(this); + mWindow.setBackgroundDrawableResource(android.R.color.transparent); + + // While the media controller is up, the volume control keys should + // affect the media stream type + mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC); + + setFocusable(true); + setFocusableInTouchMode(true); + setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); + requestFocus(); + } + */ + + /* + // Allocate and initialize the static parts of mDecorLayoutParams. Must + // also call updateFloatingWindowLayout() to fill in the dynamic parts + // (y and width) before mDecorLayoutParams can be used. + private void initFloatingWindowLayout() { + mDecorLayoutParams = new WindowManager.LayoutParams(); + WindowManager.LayoutParams p = mDecorLayoutParams; + p.gravity = Gravity.TOP; + p.height = LayoutParams.WRAP_CONTENT; + p.x = 0; + p.format = PixelFormat.TRANSLUCENT; + p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; + p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; + p.token = null; + p.windowAnimations = 0; // android.R.style.DropDownAnimationDown; + } + */ + + // Update the dynamic parts of mDecorLayoutParams + // Must be called with mAnchor != NULL. + /* + private void updateFloatingWindowLayout() { + int [] anchorPos = new int[2]; + mAnchor.getLocationOnScreen(anchorPos); + + WindowManager.LayoutParams p = mDecorLayoutParams; + p.width = mAnchor.getWidth(); + p.y = anchorPos[1] + mAnchor.getHeight(); + } + */ + + /* + // This is called whenever mAnchor's layout bound changes + public void onLayoutChange(View v, int left, int top, int right, + int bottom, int oldLeft, int oldTop, int oldRight, + int oldBottom) { + //updateFloatingWindowLayout(); + if (mShowing) { + mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams); + } + } + */ + + /* + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (mShowing) { + hide(); + } + } + return false; + } + */ + + + public void setMediaPlayer(MediaPlayerControl player) { + mPlayer = player; + mHandler.sendEmptyMessage(SHOW_PROGRESS); + updatePausePlay(); + } + + + private void initControllerView(View v) { + mPauseButton = (ImageButton) v.findViewById(R.id.playBtn); + if (mPauseButton != null) { + mPauseButton.requestFocus(); + mPauseButton.setOnClickListener(this); + } + + mFfwdButton = (ImageButton) v.findViewById(R.id.forwardBtn); + if (mFfwdButton != null) { + mFfwdButton.setOnClickListener(this); + } + + mRewButton = (ImageButton) v.findViewById(R.id.rewindBtn); + if (mRewButton != null) { + mRewButton.setOnClickListener(this); + } + + mProgress = (ProgressBar) v.findViewById(R.id.progressBar); + if (mProgress != null) { + if (mProgress instanceof SeekBar) { + SeekBar seeker = (SeekBar) mProgress; + seeker.setOnSeekBarChangeListener(this); + } + mProgress.setMax(1000); + } + + mEndTime = (TextView) v.findViewById(R.id.totalTimeText); + mCurrentTime = (TextView) v.findViewById(R.id.currentTimeText); + mFormatBuilder = new StringBuilder(); + mFormatter = new Formatter(mFormatBuilder, Locale.getDefault()); + + } + + + /** + * Disable pause or seek buttons if the stream cannot be paused or seeked. + * This requires the control interface to be a MediaPlayerControlExt + */ + private void disableUnsupportedButtons() { + try { + if (mPauseButton != null && !mPlayer.canPause()) { + mPauseButton.setEnabled(false); + } + if (mRewButton != null && !mPlayer.canSeekBackward()) { + mRewButton.setEnabled(false); + } + if (mFfwdButton != null && !mPlayer.canSeekForward()) { + mFfwdButton.setEnabled(false); + } + } catch (IncompatibleClassChangeError ex) { + // We were given an old version of the interface, that doesn't have + // the canPause/canSeekXYZ methods. This is OK, it just means we + // assume the media can be paused and seeked, and so we don't disable + // the buttons. + } + } + + + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + int pos; + switch (msg.what) { + case SHOW_PROGRESS: + pos = setProgress(); + if (!mDragging) { + msg = obtainMessage(SHOW_PROGRESS); + sendMessageDelayed(msg, 1000 - (pos % 1000)); + } + break; + } + } + }; + + private String stringForTime(int timeMs) { + int totalSeconds = timeMs / 1000; + + int seconds = totalSeconds % 60; + int minutes = (totalSeconds / 60) % 60; + int hours = totalSeconds / 3600; + + mFormatBuilder.setLength(0); + if (hours > 0) { + return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString(); + } else { + return mFormatter.format("%02d:%02d", minutes, seconds).toString(); + } + } + + private int setProgress() { + if (mPlayer == null || mDragging) { + return 0; + } + int position = mPlayer.getCurrentPosition(); + int duration = mPlayer.getDuration(); + if (mProgress != null) { + if (duration > 0) { + // use long to avoid overflow + long pos = 1000L * position / duration; + mProgress.setProgress( (int) pos); + } + int percent = mPlayer.getBufferPercentage(); + mProgress.setSecondaryProgress(percent * 10); + } + + if (mEndTime != null) + mEndTime.setText(stringForTime(duration)); + if (mCurrentTime != null) + mCurrentTime.setText(stringForTime(position)); + + return position; + } + + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + int keyCode = event.getKeyCode(); + final boolean uniqueDown = event.getRepeatCount() == 0 + && event.getAction() == KeyEvent.ACTION_DOWN; + if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK + || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE + || keyCode == KeyEvent.KEYCODE_SPACE) { + if (uniqueDown) { + doPauseResume(); + //show(sDefaultTimeout); + if (mPauseButton != null) { + mPauseButton.requestFocus(); + } + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { + if (uniqueDown && !mPlayer.isPlaying()) { + mPlayer.start(); + updatePausePlay(); + //show(sDefaultTimeout); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP + || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { + if (uniqueDown && mPlayer.isPlaying()) { + mPlayer.pause(); + updatePausePlay(); + //show(sDefaultTimeout); + } + return true; + } + + //show(sDefaultTimeout); + return super.dispatchKeyEvent(event); + } + + public void updatePausePlay() { + if (mRoot == null || mPauseButton == null) + return; + + if (mPlayer.isPlaying()) { + mPauseButton.setImageResource(android.R.drawable.ic_media_pause); + } else { + mPauseButton.setImageResource(android.R.drawable.ic_media_play); + } + } + + private void doPauseResume() { + if (mPlayer.isPlaying()) { + mPlayer.pause(); + } else { + mPlayer.start(); + } + updatePausePlay(); + } + + @Override + public void setEnabled(boolean enabled) { + if (mPauseButton != null) { + mPauseButton.setEnabled(enabled); + } + if (mFfwdButton != null) { + mFfwdButton.setEnabled(enabled); + } + if (mRewButton != null) { + mRewButton.setEnabled(enabled); + } + if (mProgress != null) { + mProgress.setEnabled(enabled); + } + disableUnsupportedButtons(); + super.setEnabled(enabled); + } + + @Override + public void onClick(View v) { + int pos; + boolean playing = mPlayer.isPlaying(); + switch (v.getId()) { + + case R.id.playBtn: + doPauseResume(); + break; + + case R.id.rewindBtn: + pos = mPlayer.getCurrentPosition(); + pos -= 5000; + mPlayer.seekTo(pos); + if (!playing) mPlayer.pause(); // necessary in some 2.3.x devices + setProgress(); + break; + + case R.id.forwardBtn: + pos = mPlayer.getCurrentPosition(); + pos += 15000; + mPlayer.seekTo(pos); + if (!playing) mPlayer.pause(); // necessary in some 2.3.x devices + setProgress(); + break; + + } + } + + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (!fromUser) { + // We're not interested in programmatically generated changes to + // the progress bar's position. + return; + } + + long duration = mPlayer.getDuration(); + long newposition = (duration * progress) / 1000L; + mPlayer.seekTo( (int) newposition); + if (mCurrentTime != null) + mCurrentTime.setText(stringForTime( (int) newposition)); + } + + /** + * Called in devices with touchpad when the user starts to adjust the + * position of the seekbar's thumb. + * + * Will be followed by several onProgressChanged notifications. + */ + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + mDragging = true; // monitors the duration of dragging + mHandler.removeMessages(SHOW_PROGRESS); // grants no more updates with media player progress while dragging + } + + + /** + * Called in devices with touchpad when the user finishes the + * adjusting of the seekbar. + */ + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + mDragging = false; + setProgress(); + updatePausePlay(); + mHandler.sendEmptyMessage(SHOW_PROGRESS); // grants future updates with media player progress + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(MediaControlView.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(MediaControlView.class.getName()); + } + +} diff --git a/src/com/owncloud/android/media/MediaService.java b/src/com/owncloud/android/media/MediaService.java new file mode 100644 index 00000000..eb57222c --- /dev/null +++ b/src/com/owncloud/android/media/MediaService.java @@ -0,0 +1,706 @@ +/* ownCloud Android client application + * 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.media; + +import android.accounts.Account; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnCompletionListener; +import android.media.MediaPlayer.OnErrorListener; +import android.media.MediaPlayer.OnPreparedListener; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiManager.WifiLock; +import android.os.IBinder; +import android.os.PowerManager; +import android.widget.Toast; + +import java.io.IOException; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.activity.FileDisplayActivity; + + +/** + * Service that handles media playback, both audio and video. + * + * Waits for Intents which signal the service to perform specific operations: Play, Pause, + * Rewind, etc. + * + * @author David A. Velasco + */ +public class MediaService extends Service implements OnCompletionListener, OnPreparedListener, + OnErrorListener, AudioManager.OnAudioFocusChangeListener { + + private static final String TAG = MediaService.class.getSimpleName(); + + private static final String MY_PACKAGE = MediaService.class.getPackage() != null ? MediaService.class.getPackage().getName() : "com.owncloud.android.media"; + + /// Intent actions that we are prepared to handle + public static final String ACTION_PLAY_FILE = MY_PACKAGE + ".action.PLAY_FILE"; + public static final String ACTION_STOP_ALL = MY_PACKAGE + ".action.STOP_ALL"; + + /// Keys to add extras to the action + public static final String EXTRA_FILE = MY_PACKAGE + ".extra.FILE"; + public static final String EXTRA_ACCOUNT = MY_PACKAGE + ".extra.ACCOUNT"; + public static String EXTRA_START_POSITION = MY_PACKAGE + ".extra.START_POSITION"; + public static final String EXTRA_PLAY_ON_LOAD = MY_PACKAGE + ".extra.PLAY_ON_LOAD"; + + + /** Error code for specific messages - see regular error codes at {@link MediaPlayer} */ + public static final int OC_MEDIA_ERROR = 0; + + /** Time To keep the control panel visible when the user does not use it */ + public static final int MEDIA_CONTROL_SHORT_LIFE = 4000; + + /** Time To keep the control panel visible when the user does not use it */ + public static final int MEDIA_CONTROL_PERMANENT = 0; + + /** Volume to set when audio focus is lost and ducking is allowed */ + private static final float DUCK_VOLUME = 0.1f; + + /** Media player instance */ + private MediaPlayer mPlayer = null; + + /** Reference to the system AudioManager */ + private AudioManager mAudioManager = null; + + + /** Values to indicate the state of the service */ + enum State { + STOPPED, + PREPARING, + PLAYING, + PAUSED + }; + + + /** Current state */ + private State mState = State.STOPPED; + + /** Possible focus values */ + enum AudioFocus { + NO_FOCUS, + NO_FOCUS_CAN_DUCK, + FOCUS + } + + /** Current focus state */ + private AudioFocus mAudioFocus = AudioFocus.NO_FOCUS; + + + /** 'True' when the current song is streaming from the network */ + private boolean mIsStreaming = false; + + /** Wifi lock kept to prevents the device from shutting off the radio when streaming a file. */ + private WifiLock mWifiLock; + + private static final String MEDIA_WIFI_LOCK_TAG = MY_PACKAGE + ".WIFI_LOCK"; + + /** Notification to keep in the notification bar while a song is playing */ + private NotificationManager mNotificationManager; + private Notification mNotification = null; + + /** File being played */ + private OCFile mFile; + + /** Account holding the file being played */ + private Account mAccount; + + /** Flag signaling if the audio should be played immediately when the file is prepared */ + protected boolean mPlayOnPrepared; + + /** Position, in miliseconds, where the audio should be started */ + private int mStartPosition; + + /** Interface to access the service through binding */ + private IBinder mBinder; + + /** Control panel shown to the user to control the playback, to register through binding */ + private MediaControlView mMediaController; + + + + /** + * Helper method to get an error message suitable to show to users for errors occurred in media playback, + * + * @param context A context to access string resources. + * @param what See {@link MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int) + * @param extra See {@link MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int) + * @return Message suitable to users. + */ + public static String getMessageForMediaError(Context context, int what, int extra) { + int messageId; + + if (what == OC_MEDIA_ERROR) { + messageId = extra; + + } else if (extra == MediaPlayer.MEDIA_ERROR_UNSUPPORTED) { + /* Added in API level 17 + Bitstream is conforming to the related coding standard or file spec, but the media framework does not support the feature. + Constant Value: -1010 (0xfffffc0e) + */ + messageId = R.string.media_err_unsupported; + + } else if (extra == MediaPlayer.MEDIA_ERROR_IO) { + /* Added in API level 17 + File or network related operation errors. + Constant Value: -1004 (0xfffffc14) + */ + messageId = R.string.media_err_io; + + } else if (extra == MediaPlayer.MEDIA_ERROR_MALFORMED) { + /* Added in API level 17 + Bitstream is not conforming to the related coding standard or file spec. + Constant Value: -1007 (0xfffffc11) + */ + messageId = R.string.media_err_malformed; + + } else if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) { + /* Added in API level 17 + Some operation takes too long to complete, usually more than 3-5 seconds. + Constant Value: -110 (0xffffff92) + */ + messageId = R.string.media_err_timeout; + + } else if (what == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { + /* Added in API level 3 + The video is streamed and its container is not valid for progressive playback i.e the video's index (e.g moov atom) is not at the start of the file. + Constant Value: 200 (0x000000c8) + */ + messageId = R.string.media_err_invalid_progressive_playback; + + } else { + /* MediaPlayer.MEDIA_ERROR_UNKNOWN + Added in API level 1 + Unspecified media player error. + Constant Value: 1 (0x00000001) + */ + /* MediaPlayer.MEDIA_ERROR_SERVER_DIED) + Added in API level 1 + Media server died. In this case, the application must release the MediaPlayer object and instantiate a new one. + Constant Value: 100 (0x00000064) + */ + messageId = R.string.media_err_unknown; + } + return context.getString(messageId); + } + + + + /** + * Initialize a service instance + * + * {@inheritDoc} + */ + @Override + public void onCreate() { + Log_OC.d(TAG, "Creating ownCloud media service"); + + mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)). + createWifiLock(WifiManager.WIFI_MODE_FULL, MEDIA_WIFI_LOCK_TAG); + + mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE); + mBinder = new MediaServiceBinder(this); + } + + + /** + * Entry point for Intents requesting actions, sent here via startService. + * + * {@inheritDoc} + */ + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + String action = intent.getAction(); + if (action.equals(ACTION_PLAY_FILE)) { + processPlayFileRequest(intent); + + } else if (action.equals(ACTION_STOP_ALL)) { + processStopRequest(true); + } + + return START_NOT_STICKY; // don't want it to restart in case it's killed. + } + + + /** + * Processes a request to play a media file received as a parameter + * + * TODO If a new request is received when a file is being prepared, it is ignored. Is this what we want? + * + * @param intent Intent received in the request with the data to identify the file to play. + */ + private void processPlayFileRequest(Intent intent) { + if (mState != State.PREPARING) { + mFile = intent.getExtras().getParcelable(EXTRA_FILE); + mAccount = intent.getExtras().getParcelable(EXTRA_ACCOUNT); + mPlayOnPrepared = intent.getExtras().getBoolean(EXTRA_PLAY_ON_LOAD, false); + mStartPosition = intent.getExtras().getInt(EXTRA_START_POSITION, 0); + tryToGetAudioFocus(); + playMedia(); + } + } + + + /** + * Processes a request to play a media file. + */ + protected void processPlayRequest() { + // request audio focus + tryToGetAudioFocus(); + + // actually play the song + if (mState == State.STOPPED) { + // (re)start playback + playMedia(); + + } else if (mState == State.PAUSED) { + // continue playback + mState = State.PLAYING; + setUpAsForeground(String.format(getString(R.string.media_state_playing), mFile.getFileName())); + configAndStartMediaPlayer(); + + } + } + + + /** + * Makes sure the media player exists and has been reset. This will create the media player + * if needed. reset the existing media player if one already exists. + */ + protected void createMediaPlayerIfNeeded() { + if (mPlayer == null) { + mPlayer = new MediaPlayer(); + + // make sure the CPU won't go to sleep while media is playing + mPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); + + // the media player will notify the service when it's ready preparing, and when it's done playing + mPlayer.setOnPreparedListener(this); + mPlayer.setOnCompletionListener(this); + mPlayer.setOnErrorListener(this); + + } else { + mPlayer.reset(); + } + } + + /** + * Processes a request to pause the current playback + */ + protected void processPauseRequest() { + if (mState == State.PLAYING) { + mState = State.PAUSED; + mPlayer.pause(); + releaseResources(false); // retain media player in pause + // TODO polite audio focus, instead of keep it owned; or not? + } + } + + + /** + * Processes a request to stop the playback. + * + * @param force When 'true', the playback is stopped no matter the value of mState + */ + protected void processStopRequest(boolean force) { + if (mState != State.PREPARING || force) { + mState = State.STOPPED; + mFile = null; + mAccount = null; + releaseResources(true); + giveUpAudioFocus(); + stopSelf(); // service is no longer necessary + } + } + + + /** + * Releases resources used by the service for playback. This includes the "foreground service" + * status and notification, the wake locks and possibly the MediaPlayer. + * + * @param releaseMediaPlayer Indicates whether the Media Player should also be released or not + */ + protected void releaseResources(boolean releaseMediaPlayer) { + // stop being a foreground service + stopForeground(true); + + // stop and release the Media Player, if it's available + if (releaseMediaPlayer && mPlayer != null) { + mPlayer.reset(); + mPlayer.release(); + mPlayer = null; + } + + // release the Wifi lock, if holding it + if (mWifiLock.isHeld()) { + mWifiLock.release(); + } + } + + + /** + * Fully releases the audio focus. + */ + private void giveUpAudioFocus() { + if (mAudioFocus == AudioFocus.FOCUS + && mAudioManager != null + && AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.abandonAudioFocus(this)) { + + mAudioFocus = AudioFocus.NO_FOCUS; + } + } + + + /** + * Reconfigures MediaPlayer according to audio focus settings and starts/restarts it. + */ + protected void configAndStartMediaPlayer() { + if (mPlayer == null) { + throw new IllegalStateException("mPlayer is NULL"); + } + + if (mAudioFocus == AudioFocus.NO_FOCUS) { + if (mPlayer.isPlaying()) { + mPlayer.pause(); // have to be polite; but mState is not changed, to resume when focus is received again + } + + } else { + if (mAudioFocus == AudioFocus.NO_FOCUS_CAN_DUCK) { + mPlayer.setVolume(DUCK_VOLUME, DUCK_VOLUME); + + } else { + mPlayer.setVolume(1.0f, 1.0f); // full volume + } + + if (!mPlayer.isPlaying()) { + mPlayer.start(); + } + } + } + + + /** + * Requests the audio focus to the Audio Manager + */ + private void tryToGetAudioFocus() { + if (mAudioFocus != AudioFocus.FOCUS + && mAudioManager != null + && (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.requestAudioFocus( this, + AudioManager.STREAM_MUSIC, + AudioManager.AUDIOFOCUS_GAIN)) + ) { + mAudioFocus = AudioFocus.FOCUS; + } + } + + + /** + * Starts playing the current media file. + */ + protected void playMedia() { + mState = State.STOPPED; + releaseResources(false); // release everything except MediaPlayer + + try { + if (mFile == null) { + Toast.makeText(this, R.string.media_err_nothing_to_play, Toast.LENGTH_LONG).show(); + processStopRequest(true); + return; + + } else if (mAccount == null) { + Toast.makeText(this, R.string.media_err_not_in_owncloud, Toast.LENGTH_LONG).show(); + processStopRequest(true); + return; + } + + createMediaPlayerIfNeeded(); + mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + String url = mFile.getStoragePath(); + /* Streaming is not possible right now + if (url == null || url.length() <= 0) { + url = AccountUtils.constructFullURLForAccount(this, mAccount) + mFile.getRemotePath(); + } + mIsStreaming = url.startsWith("http:") || url.startsWith("https:"); + */ + mIsStreaming = false; + + mPlayer.setDataSource(url); + + mState = State.PREPARING; + setUpAsForeground(String.format(getString(R.string.media_state_loading), mFile.getFileName())); + + // starts preparing the media player in background + mPlayer.prepareAsync(); + + // prevent the Wifi from going to sleep when streaming + if (mIsStreaming) { + mWifiLock.acquire(); + } else if (mWifiLock.isHeld()) { + mWifiLock.release(); + } + + } catch (SecurityException e) { + Log_OC.e(TAG, "SecurityException playing " + mAccount.name + mFile.getRemotePath(), e); + Toast.makeText(this, String.format(getString(R.string.media_err_security_ex), mFile.getFileName()), Toast.LENGTH_LONG).show(); + processStopRequest(true); + + } catch (IOException e) { + Log_OC.e(TAG, "IOException playing " + mAccount.name + mFile.getRemotePath(), e); + Toast.makeText(this, String.format(getString(R.string.media_err_io_ex), mFile.getFileName()), Toast.LENGTH_LONG).show(); + processStopRequest(true); + + } catch (IllegalStateException e) { + Log_OC.e(TAG, "IllegalStateException " + mAccount.name + mFile.getRemotePath(), e); + Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()), Toast.LENGTH_LONG).show(); + processStopRequest(true); + + } catch (IllegalArgumentException e) { + Log_OC.e(TAG, "IllegalArgumentException " + mAccount.name + mFile.getRemotePath(), e); + Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()), Toast.LENGTH_LONG).show(); + processStopRequest(true); + } + } + + + /** Called when media player is done playing current song. */ + public void onCompletion(MediaPlayer player) { + Toast.makeText(this, String.format(getString(R.string.media_event_done, mFile.getFileName())), Toast.LENGTH_LONG).show(); + if (mMediaController != null) { + // somebody is still bound to the service + player.seekTo(0); + processPauseRequest(); + mMediaController.updatePausePlay(); + } else { + // nobody is bound + processStopRequest(true); + } + return; + } + + + /** + * Called when media player is done preparing. + * + * Time to start. + */ + public void onPrepared(MediaPlayer player) { + mState = State.PLAYING; + updateNotification(String.format(getString(R.string.media_state_playing), mFile.getFileName())); + if (mMediaController != null) { + mMediaController.setEnabled(true); + } + player.seekTo(mStartPosition); + configAndStartMediaPlayer(); + if (!mPlayOnPrepared) { + processPauseRequest(); + } + + if (mMediaController != null) { + mMediaController.updatePausePlay(); + } + } + + + /** + * Updates the status notification + */ + @SuppressWarnings("deprecation") + 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_ACCOUNT, mAccount); + showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), + (int)System.currentTimeMillis(), + showDetailsIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + mNotification.when = System.currentTimeMillis(); + //mNotification.contentView.setTextViewText(R.id.status_text, content); + String ticker = String.format(getString(R.string.media_notif_ticker), getString(R.string.app_name)); + mNotification.setLatestEventInfo(getApplicationContext(), ticker, content, mNotification.contentIntent); + mNotificationManager.notify(R.string.media_notif_ticker, mNotification); + } + + + /** + * Configures the service as a foreground service. + * + * The system will avoid finishing the service as much as possible when resources as low. + * + * A notification must be created to keep the user aware of the existance of the service. + */ + @SuppressWarnings("deprecation") + private void setUpAsForeground(String content) { + /// creates status notification + // TODO put a progress bar to follow the playback progress + mNotification = new Notification(); + mNotification.icon = android.R.drawable.ic_media_play; + //mNotification.tickerText = text; + mNotification.when = System.currentTimeMillis(); + mNotification.flags |= Notification.FLAG_ONGOING_EVENT; + //mNotification.contentView.setTextViewText(R.id.status_text, "ownCloud Music Player"); // NULL POINTER + //mNotification.contentView.setTextViewText(R.id.status_text, getString(R.string.downloader_download_in_progress_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_ACCOUNT, mAccount); + showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), + (int)System.currentTimeMillis(), + showDetailsIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + + + //mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotification); + String ticker = String.format(getString(R.string.media_notif_ticker), getString(R.string.app_name)); + mNotification.setLatestEventInfo(getApplicationContext(), ticker, content, mNotification.contentIntent); + startForeground(R.string.media_notif_ticker, mNotification); + + } + + /** + * Called when there's an error playing media. + * + * Warns the user about the error and resets the media player. + */ + public boolean onError(MediaPlayer mp, int what, int extra) { + Log_OC.e(TAG, "Error in audio playback, what = " + what + ", extra = " + extra); + + String message = getMessageForMediaError(this, what, extra); + Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); + + processStopRequest(true); + return true; + } + + /** + * Called by the system when another app tries to play some sound. + * + * {@inheritDoc} + */ + @Override + public void onAudioFocusChange(int focusChange) { + if (focusChange > 0) { + // focus gain; check AudioManager.AUDIOFOCUS_* values + mAudioFocus = AudioFocus.FOCUS; + // restart media player with new focus settings + if (mState == State.PLAYING) + configAndStartMediaPlayer(); + + } else if (focusChange < 0) { + // focus loss; check AudioManager.AUDIOFOCUS_* values + boolean canDuck = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK == focusChange; + mAudioFocus = canDuck ? AudioFocus.NO_FOCUS_CAN_DUCK : AudioFocus.NO_FOCUS; + // start/restart/pause media player with new focus settings + if (mPlayer != null && mPlayer.isPlaying()) + configAndStartMediaPlayer(); + } + + } + + /** + * Called when the service is finished for final clean-up. + * + * {@inheritDoc} + */ + @Override + public void onDestroy() { + mState = State.STOPPED; + releaseResources(true); + giveUpAudioFocus(); + } + + + /** + * Provides a binder object that clients can use to perform operations on the MediaPlayer managed by the MediaService. + */ + @Override + public IBinder onBind(Intent arg) { + return mBinder; + } + + + /** + * Called when ALL the bound clients were onbound. + * + * The service is destroyed if playback stopped or paused + */ + @Override + public boolean onUnbind(Intent intent) { + if (mState == State.PAUSED || mState == State.STOPPED) { + processStopRequest(false); + } + return false; // not accepting rebinding (default behaviour) + } + + + /** + * Accesses the current MediaPlayer instance in the service. + * + * To be handled carefully. Visibility is protected to be accessed only + * + * @return Current MediaPlayer instance handled by MediaService. + */ + protected MediaPlayer getPlayer() { + return mPlayer; + } + + + /** + * Accesses the current OCFile loaded in the service. + * + * @return The current OCFile loaded in the service. + */ + protected OCFile getCurrentFile() { + return mFile; + } + + + /** + * Accesses the current {@link State} of the MediaService. + * + * @return The current {@link State} of the MediaService. + */ + protected State getState() { + return mState; + } + + + protected void setMediaContoller(MediaControlView mediaController) { + mMediaController = mediaController; + } + + protected MediaControlView getMediaController() { + return mMediaController; + } + +} diff --git a/src/com/owncloud/android/media/MediaServiceBinder.java b/src/com/owncloud/android/media/MediaServiceBinder.java new file mode 100644 index 00000000..4fab8bdf --- /dev/null +++ b/src/com/owncloud/android/media/MediaServiceBinder.java @@ -0,0 +1,181 @@ +/* ownCloud Android client application + * 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.media; + + +import com.owncloud.android.Log_OC; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.media.MediaService.State; + +import android.accounts.Account; +import android.content.Intent; +import android.media.MediaPlayer; +import android.os.Binder; +import android.widget.MediaController; + + +/** + * Binder allowing client components to perform operations on on the MediaPlayer managed by a MediaService instance. + * + * Provides the operations of {@link MediaController.MediaPlayerControl}, and an extra method to check if + * an {@link OCFile} instance is handled by the MediaService. + * + * @author David A. Velasco + */ +public class MediaServiceBinder extends Binder implements MediaController.MediaPlayerControl { + + private static final String TAG = MediaServiceBinder.class.getSimpleName(); + /** + * {@link MediaService} instance to access with the binder + */ + private MediaService mService = null; + + /** + * Public constructor + * + * @param service A {@link MediaService} instance to access with the binder + */ + public MediaServiceBinder(MediaService service) { + if (service == null) { + throw new IllegalArgumentException("Argument 'service' can not be null"); + } + mService = service; + } + + + public boolean isPlaying(OCFile mFile) { + return (mFile != null && mFile.equals(mService.getCurrentFile())); + } + + + @Override + public boolean canPause() { + return true; + } + + @Override + public boolean canSeekBackward() { + return true; + } + + @Override + public boolean canSeekForward() { + return true; + } + + @Override + public int getBufferPercentage() { + MediaPlayer currentPlayer = mService.getPlayer(); + if (currentPlayer != null) { + return 100; + // TODO update for streamed playback; add OnBufferUpdateListener in MediaService + } else { + return 0; + } + } + + @Override + public int getCurrentPosition() { + MediaPlayer currentPlayer = mService.getPlayer(); + if (currentPlayer != null) { + int pos = currentPlayer.getCurrentPosition(); + return pos; + } else { + return 0; + } + } + + @Override + public int getDuration() { + MediaPlayer currentPlayer = mService.getPlayer(); + if (currentPlayer != null) { + int dur = currentPlayer.getDuration(); + return dur; + } else { + return 0; + } + } + + + /** + * Reports if the MediaService is playing a file or not. + * + * Considers that the file is being played when it is in preparation because the expected + * client of this method is a {@link MediaController} , and we do not want that the 'play' + * button is shown when the file is being prepared by the MediaService. + */ + @Override + public boolean isPlaying() { + MediaService.State currentState = mService.getState(); + return (currentState == State.PLAYING || (currentState == State.PREPARING && mService.mPlayOnPrepared)); + } + + + @Override + public void pause() { + Log_OC.d(TAG, "Pausing through binder..."); + mService.processPauseRequest(); + } + + @Override + public void seekTo(int pos) { + Log_OC.d(TAG, "Seeking " + pos + " through binder..."); + MediaPlayer currentPlayer = mService.getPlayer(); + MediaService.State currentState = mService.getState(); + if (currentPlayer != null && currentState != State.PREPARING && currentState != State.STOPPED) { + currentPlayer.seekTo(pos); + } + } + + @Override + public void start() { + Log_OC.d(TAG, "Starting through binder..."); + mService.processPlayRequest(); // this will finish the service if there is no file preloaded to play + } + + public void start(Account account, OCFile file, boolean playImmediately, int position) { + 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_PLAY_ON_LOAD, playImmediately); + i.putExtra(MediaService.EXTRA_START_POSITION, position); + i.setAction(MediaService.ACTION_PLAY_FILE); + mService.startService(i); + } + + + public void registerMediaController(MediaControlView mediaController) { + mService.setMediaContoller(mediaController); + } + + public void unregisterMediaController(MediaControlView mediaController) { + if (mediaController != null && mediaController == mService.getMediaController()) { + mService.setMediaContoller(null); + } + + } + + public boolean isInPlaybackState() { + MediaService.State currentState = mService.getState(); + return (currentState == MediaService.State.PLAYING || currentState == MediaService.State.PAUSED); + } + +} + + diff --git a/src/com/owncloud/android/network/AdvancedSslSocketFactory.java b/src/com/owncloud/android/network/AdvancedSslSocketFactory.java new file mode 100644 index 00000000..6a698713 --- /dev/null +++ b/src/com/owncloud/android/network/AdvancedSslSocketFactory.java @@ -0,0 +1,241 @@ +/* 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.network; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.UnknownHostException; +import java.security.cert.X509Certificate; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +import org.apache.commons.httpclient.ConnectTimeoutException; +import org.apache.commons.httpclient.params.HttpConnectionParams; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; +import org.apache.http.conn.ssl.X509HostnameVerifier; + +import com.owncloud.android.Log_OC; + + +/** + * AdvancedSSLProtocolSocketFactory allows to create SSL {@link Socket}s with + * a custom SSLContext and an optional Hostname Verifier. + * + * @author David A. Velasco + */ + +public class AdvancedSslSocketFactory implements ProtocolSocketFactory { + + private static final String TAG = AdvancedSslSocketFactory.class.getSimpleName(); + + private SSLContext mSslContext = null; + private AdvancedX509TrustManager mTrustManager = null; + private X509HostnameVerifier mHostnameVerifier = null; + + public SSLContext getSslContext() { + return mSslContext; + } + + /** + * Constructor for AdvancedSSLProtocolSocketFactory. + */ + public AdvancedSslSocketFactory(SSLContext sslContext, AdvancedX509TrustManager trustManager, X509HostnameVerifier hostnameVerifier) { + if (sslContext == null) + throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null SSLContext"); + if (trustManager == null) + throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null Trust Manager"); + mSslContext = sslContext; + mTrustManager = trustManager; + mHostnameVerifier = hostnameVerifier; + } + + /** + * @see ProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int) + */ + public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException { + Socket socket = mSslContext.getSocketFactory().createSocket(host, port, clientHost, clientPort); + verifyPeerIdentity(host, port, socket); + return socket; + } + + + /** + * Attempts to get a new socket connection to the given host within the + * given time limit. + * + * @param host the host name/IP + * @param port the port on the host + * @param clientHost the local host name/IP to bind the socket to + * @param clientPort the port on the local machine + * @param params {@link HttpConnectionParams Http connection parameters} + * + * @return Socket a new socket + * + * @throws IOException if an I/O error occurs while creating the socket + * @throws UnknownHostException if the IP address of the host cannot be + * determined + */ + public Socket createSocket(final String host, final int port, + final InetAddress localAddress, final int localPort, + final HttpConnectionParams params) throws IOException, + UnknownHostException, ConnectTimeoutException { + Log_OC.d(TAG, "Creating SSL Socket with remote " + host + ":" + port + ", local " + localAddress + ":" + localPort + ", params: " + params); + if (params == null) { + throw new IllegalArgumentException("Parameters may not be null"); + } + int timeout = params.getConnectionTimeout(); + SocketFactory socketfactory = mSslContext.getSocketFactory(); + Log_OC.d(TAG, " ... with connection timeout " + timeout + " and socket timeout " + params.getSoTimeout()); + Socket socket = socketfactory.createSocket(); + SocketAddress localaddr = new InetSocketAddress(localAddress, localPort); + SocketAddress remoteaddr = new InetSocketAddress(host, port); + socket.setSoTimeout(params.getSoTimeout()); + socket.bind(localaddr); + socket.connect(remoteaddr, timeout); + verifyPeerIdentity(host, port, socket); + return socket; + } + + /** + * @see ProtocolSocketFactory#createSocket(java.lang.String,int) + */ + public Socket createSocket(String host, int port) throws IOException, + UnknownHostException { + Log_OC.d(TAG, "Creating SSL Socket with remote " + host + ":" + port); + Socket socket = mSslContext.getSocketFactory().createSocket(host, port); + verifyPeerIdentity(host, port, socket); + return socket; + } + + public boolean equals(Object obj) { + return ((obj != null) && obj.getClass().equals( + AdvancedSslSocketFactory.class)); + } + + public int hashCode() { + return AdvancedSslSocketFactory.class.hashCode(); + } + + + public X509HostnameVerifier getHostNameVerifier() { + return mHostnameVerifier; + } + + + public void setHostNameVerifier(X509HostnameVerifier hostnameVerifier) { + mHostnameVerifier = hostnameVerifier; + } + + /** + * Verifies the identity of the server. + * + * The server certificate is verified first. + * + * Then, the host name is compared with the content of the server certificate using the current host name verifier, if any. + * @param socket + */ + private void verifyPeerIdentity(String host, int port, Socket socket) throws IOException { + try { + CertificateCombinedException failInHandshake = null; + /// 1. VERIFY THE SERVER CERTIFICATE through the registered TrustManager (that should be an instance of AdvancedX509TrustManager) + try { + SSLSocket sock = (SSLSocket) socket; // a new SSLSession instance is created as a "side effect" + sock.startHandshake(); + + } catch (RuntimeException e) { + + if (e instanceof CertificateCombinedException) { + failInHandshake = (CertificateCombinedException) e; + } else { + Throwable cause = e.getCause(); + Throwable previousCause = null; + while (cause != null && cause != previousCause && !(cause instanceof CertificateCombinedException)) { + previousCause = cause; + cause = cause.getCause(); + } + if (cause != null && cause instanceof CertificateCombinedException) { + failInHandshake = (CertificateCombinedException)cause; + } + } + if (failInHandshake == null) { + throw e; + } + failInHandshake.setHostInUrl(host); + + } + + /// 2. VERIFY HOSTNAME + SSLSession newSession = null; + boolean verifiedHostname = true; + if (mHostnameVerifier != null) { + if (failInHandshake != null) { + /// 2.1 : a new SSLSession instance was NOT created in the handshake + X509Certificate serverCert = failInHandshake.getServerCertificate(); + try { + mHostnameVerifier.verify(host, serverCert); + } catch (SSLException e) { + verifiedHostname = false; + } + + } else { + /// 2.2 : a new SSLSession instance was created in the handshake + newSession = ((SSLSocket)socket).getSession(); + if (!mTrustManager.isKnownServer((X509Certificate)(newSession.getPeerCertificates()[0]))) { + verifiedHostname = mHostnameVerifier.verify(host, newSession); + } + } + } + + /// 3. Combine the exceptions to throw, if any + if (!verifiedHostname) { + SSLPeerUnverifiedException pue = new SSLPeerUnverifiedException("Names in the server certificate do not match to " + host + " in the URL"); + if (failInHandshake == null) { + failInHandshake = new CertificateCombinedException((X509Certificate) newSession.getPeerCertificates()[0]); + failInHandshake.setHostInUrl(host); + } + failInHandshake.setSslPeerUnverifiedException(pue); + pue.initCause(failInHandshake); + throw pue; + + } else if (failInHandshake != null) { + SSLHandshakeException hse = new SSLHandshakeException("Server certificate could not be verified"); + hse.initCause(failInHandshake); + throw hse; + } + + } catch (IOException io) { + try { + socket.close(); + } catch (Exception x) { + // NOTHING - irrelevant exception for the caller + } + throw io; + } + } + +} diff --git a/src/com/owncloud/android/network/AdvancedX509TrustManager.java b/src/com/owncloud/android/network/AdvancedX509TrustManager.java new file mode 100644 index 00000000..81dcdbd7 --- /dev/null +++ b/src/com/owncloud/android/network/AdvancedX509TrustManager.java @@ -0,0 +1,147 @@ +/* ownCloud Android client application + * 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.network; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertPathValidatorException; +import java.security.cert.CertStoreException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import com.owncloud.android.Log_OC; + + +/** + * @author David A. Velasco + */ +public class AdvancedX509TrustManager implements X509TrustManager { + + private static final String TAG = AdvancedX509TrustManager.class.getSimpleName(); + + private X509TrustManager mStandardTrustManager = null; + private KeyStore mKnownServersKeyStore; + + /** + * Constructor for AdvancedX509TrustManager + * + * @param knownServersCertStore Local certificates store with server certificates explicitly trusted by the user. + * @throws CertStoreException When no default X509TrustManager instance was found in the system. + */ + public AdvancedX509TrustManager(KeyStore knownServersKeyStore) + throws NoSuchAlgorithmException, KeyStoreException, CertStoreException { + super(); + TrustManagerFactory factory = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + factory.init((KeyStore)null); + mStandardTrustManager = findX509TrustManager(factory); + + mKnownServersKeyStore = knownServersKeyStore; + } + + + /** + * Locates the first X509TrustManager provided by a given TrustManagerFactory + * @param factory TrustManagerFactory to inspect in the search for a X509TrustManager + * @return The first X509TrustManager found in factory. + * @throws CertStoreException When no X509TrustManager instance was found in factory + */ + private X509TrustManager findX509TrustManager(TrustManagerFactory factory) throws CertStoreException { + TrustManager tms[] = factory.getTrustManagers(); + for (int i = 0; i < tms.length; i++) { + if (tms[i] instanceof X509TrustManager) { + return (X509TrustManager) tms[i]; + } + } + return null; + } + + + /** + * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[], + * String authType) + */ + public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException { + mStandardTrustManager.checkClientTrusted(certificates, authType); + } + + + /** + * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], + * String authType) + */ + public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException { + if (!isKnownServer(certificates[0])) { + CertificateCombinedException result = new CertificateCombinedException(certificates[0]); + try { + certificates[0].checkValidity(); + } catch (CertificateExpiredException c) { + result.setCertificateExpiredException(c); + + } catch (CertificateNotYetValidException c) { + result.setCertificateNotYetException(c); + } + + try { + mStandardTrustManager.checkServerTrusted(certificates, authType); + } catch (CertificateException c) { + Throwable cause = c.getCause(); + Throwable previousCause = null; + while (cause != null && cause != previousCause && !(cause instanceof CertPathValidatorException)) { // getCause() is not funny + previousCause = cause; + cause = cause.getCause(); + } + if (cause != null && cause instanceof CertPathValidatorException) { + result.setCertPathValidatorException((CertPathValidatorException)cause); + } else { + result.setOtherCertificateException(c); + } + } + + if (result.isException()) + throw result; + + } + } + + + /** + * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() + */ + public X509Certificate[] getAcceptedIssuers() { + return mStandardTrustManager.getAcceptedIssuers(); + } + + + public boolean isKnownServer(X509Certificate cert) { + try { + return (mKnownServersKeyStore.getCertificateAlias(cert) != null); + } catch (KeyStoreException e) { + Log_OC.d(TAG, "Fail while checking certificate in the known-servers store"); + return false; + } + } + +} \ No newline at end of file diff --git a/src/com/owncloud/android/network/BearerAuthScheme.java b/src/com/owncloud/android/network/BearerAuthScheme.java new file mode 100644 index 00000000..16791c23 --- /dev/null +++ b/src/com/owncloud/android/network/BearerAuthScheme.java @@ -0,0 +1,269 @@ +/* ownCloud Android client application + * Copyright (C) 2012 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.network; + +import java.util.Map; + +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.auth.AuthChallengeParser; +import org.apache.commons.httpclient.auth.AuthScheme; +import org.apache.commons.httpclient.auth.AuthenticationException; +import org.apache.commons.httpclient.auth.InvalidCredentialsException; +import org.apache.commons.httpclient.auth.MalformedChallengeException; + +import com.owncloud.android.Log_OC; + + +/** + * Bearer authentication scheme as defined in RFC 6750. + * + * @author David A. Velasco + */ + +public class BearerAuthScheme implements AuthScheme /*extends RFC2617Scheme*/ { + + private static final String TAG = BearerAuthScheme.class.getSimpleName(); + + public static final String AUTH_POLICY = "Bearer"; + + /** Whether the bearer authentication process is complete */ + private boolean mComplete; + + /** Authentication parameter map */ + private Map mParams = null; + + + /** + * Default constructor for the bearer authentication scheme. + */ + public BearerAuthScheme() { + mComplete = false; + } + + /** + * Constructor for the basic authentication scheme. + * + * @param challenge Authentication challenge + * + * @throws MalformedChallengeException Thrown if the authentication challenge is malformed + * + * @deprecated Use parameterless constructor and {@link AuthScheme#processChallenge(String)} method + */ + public BearerAuthScheme(final String challenge) throws MalformedChallengeException { + processChallenge(challenge); + mComplete = true; + } + + /** + * Returns textual designation of the bearer authentication scheme. + * + * @return "Bearer" + */ + public String getSchemeName() { + return "bearer"; + } + + /** + * Processes the Bearer challenge. + * + * @param challenge The challenge string + * + * @throws MalformedChallengeException Thrown if the authentication challenge is malformed + */ + public void processChallenge(String challenge) throws MalformedChallengeException { + String s = AuthChallengeParser.extractScheme(challenge); + if (!s.equalsIgnoreCase(getSchemeName())) { + throw new MalformedChallengeException( + "Invalid " + getSchemeName() + " challenge: " + challenge); + } + mParams = AuthChallengeParser.extractParams(challenge); + mComplete = true; + } + + /** + * Tests if the Bearer authentication process has been completed. + * + * @return 'true' if Bearer authorization has been processed, 'false' otherwise. + */ + public boolean isComplete() { + return this.mComplete; + } + + /** + * Produces bearer authorization string for the given set of + * {@link Credentials}. + * + * @param credentials The set of credentials to be used for authentication + * @param method Method name is ignored by the bearer authentication scheme + * @param uri URI is ignored by the bearer authentication scheme + * @throws InvalidCredentialsException If authentication credentials are not valid or not applicable + * for this authentication scheme + * @throws AuthenticationException If authorization string cannot be generated due to an authentication failure + * @return A bearer authorization string + * + * @deprecated Use {@link #authenticate(Credentials, HttpMethod)} + */ + public String authenticate(Credentials credentials, String method, String uri) throws AuthenticationException { + Log_OC.d(TAG, "enter BearerScheme.authenticate(Credentials, String, String)"); + + BearerCredentials bearer = null; + try { + bearer = (BearerCredentials) credentials; + } catch (ClassCastException e) { + throw new InvalidCredentialsException( + "Credentials cannot be used for bearer authentication: " + + credentials.getClass().getName()); + } + return BearerAuthScheme.authenticate(bearer); + } + + + /** + * Returns 'false'. Bearer authentication scheme is request based. + * + * @return 'false'. + */ + public boolean isConnectionBased() { + return false; + } + + /** + * Produces bearer authorization string for the given set of {@link Credentials}. + * + * @param credentials The set of credentials to be used for authentication + * @param method The method being authenticated + * @throws InvalidCredentialsException If authentication credentials are not valid or not applicable for this authentication + * scheme. + * @throws AuthenticationException If authorization string cannot be generated due to an authentication failure. + * + * @return a basic authorization string + */ + public String authenticate(Credentials credentials, HttpMethod method) throws AuthenticationException { + Log_OC.d(TAG, "enter BearerScheme.authenticate(Credentials, HttpMethod)"); + + if (method == null) { + throw new IllegalArgumentException("Method may not be null"); + } + BearerCredentials bearer = null; + try { + bearer = (BearerCredentials) credentials; + } catch (ClassCastException e) { + throw new InvalidCredentialsException( + "Credentials cannot be used for bearer authentication: " + + credentials.getClass().getName()); + } + return BearerAuthScheme.authenticate( + bearer, + method.getParams().getCredentialCharset()); + } + + /** + * @deprecated Use {@link #authenticate(BearerCredentials, String)} + * + * Returns a bearer Authorization header value for the given + * {@link BearerCredentials}. + * + * @param credentials The credentials to encode. + * + * @return A bearer authorization string + */ + public static String authenticate(BearerCredentials credentials) { + return authenticate(credentials, "ISO-8859-1"); + } + + /** + * Returns a bearer Authorization header value for the given + * {@link BearerCredentials} and charset. + * + * @param credentials The credentials to encode. + * @param charset The charset to use for encoding the credentials + * + * @return A bearer authorization string + * + * @since 3.0 + */ + public static String authenticate(BearerCredentials credentials, String charset) { + Log_OC.d(TAG, "enter BearerAuthScheme.authenticate(BearerCredentials, String)"); + + if (credentials == null) { + throw new IllegalArgumentException("Credentials may not be null"); + } + if (charset == null || charset.length() == 0) { + throw new IllegalArgumentException("charset may not be null or empty"); + } + StringBuffer buffer = new StringBuffer(); + buffer.append(credentials.getAccessToken()); + + //return "Bearer " + EncodingUtil.getAsciiString(EncodingUtil.getBytes(buffer.toString(), charset)); + return "Bearer " + buffer.toString(); + } + + /** + * Returns a String identifying the authentication challenge. This is + * used, in combination with the host and port to determine if + * authorization has already been attempted or not. Schemes which + * require multiple requests to complete the authentication should + * return a different value for each stage in the request. + * + * Additionally, the ID should take into account any changes to the + * authentication challenge and return a different value when appropriate. + * For example when the realm changes in basic authentication it should be + * considered a different authentication attempt and a different value should + * be returned. + * + * This method simply returns the realm for the challenge. + * + * @return String a String identifying the authentication challenge. + * + * @deprecated no longer used + */ + @Override + public String getID() { + return getRealm(); + } + + /** + * Returns authentication parameter with the given name, if available. + * + * @param name The name of the parameter to be returned + * + * @return The parameter with the given name + */ + @Override + public String getParameter(String name) { + if (name == null) { + throw new IllegalArgumentException("Parameter name may not be null"); + } + if (mParams == null) { + return null; + } + return (String) mParams.get(name.toLowerCase()); + } + + /** + * Returns authentication realm. The realm may not be null. + * + * @return The authentication realm + */ + @Override + public String getRealm() { + return getParameter("realm"); + } + +} diff --git a/src/com/owncloud/android/network/BearerCredentials.java b/src/com/owncloud/android/network/BearerCredentials.java new file mode 100644 index 00000000..50799b02 --- /dev/null +++ b/src/com/owncloud/android/network/BearerCredentials.java @@ -0,0 +1,97 @@ +/* ownCloud Android client application + * Copyright (C) 2012 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.network; + +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.util.LangUtils; + +/** + * Bearer token {@link Credentials} + * + * @author David A. Velasco + */ +public class BearerCredentials implements Credentials { + + + private String mAccessToken; + + + /** + * The constructor with the bearer token + * + * @param token The bearer token + */ + public BearerCredentials(String token) { + /*if (token == null) { + throw new IllegalArgumentException("Bearer token may not be null"); + }*/ + mAccessToken = (token == null) ? "" : token; + } + + + /** + * Returns the access token + * + * @return The access token + */ + public String getAccessToken() { + return mAccessToken; + } + + + /** + * Get this object string. + * + * @return The access token + */ + public String toString() { + return mAccessToken; + } + + /** + * Does a hash of the access token. + * + * @return The hash code of the access token + */ + public int hashCode() { + int hash = LangUtils.HASH_SEED; + hash = LangUtils.hashCode(hash, mAccessToken); + return hash; + } + + /** + * These credentials are assumed equal if accessToken is the same. + * + * @param o The other object to compare with. + * + * @return 'True' if the object is equivalent. + */ + public boolean equals(Object o) { + if (o == null) return false; + if (this == o) return true; + if (this.getClass().equals(o.getClass())) { + BearerCredentials that = (BearerCredentials) o; + if (LangUtils.equals(mAccessToken, that.mAccessToken)) { + return true; + } + } + return false; + } + +} + diff --git a/src/com/owncloud/android/network/CertificateCombinedException.java b/src/com/owncloud/android/network/CertificateCombinedException.java new file mode 100644 index 00000000..e96d9dc6 --- /dev/null +++ b/src/com/owncloud/android/network/CertificateCombinedException.java @@ -0,0 +1,130 @@ +/* ownCloud Android client application + * 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.network; + +import java.security.cert.CertPathValidatorException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLPeerUnverifiedException; + +/** + * Exception joining all the problems that {@link AdvancedX509TrustManager} can find in + * a certificate chain for a server. + * + * This was initially created as an extension of CertificateException, but some + * implementations of the SSL socket layer in existing devices are REPLACING the CertificateException + * instances thrown by {@link javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], String)} + * with SSLPeerUnverifiedException FORGETTING THE CAUSING EXCEPTION instead of wrapping it. + * + * Due to this, extending RuntimeException is necessary to get that the CertificateCombinedException + * instance reaches {@link AdvancedSslSocketFactory#verifyPeerIdentity}. + * + * BE CAREFUL. As a RuntimeException extensions, Java compilers do not require to handle it + * in client methods. Be sure to use it only when you know exactly where it will go. + * + * @author David A. Velasco + */ +public class CertificateCombinedException extends RuntimeException { + + /** Generated - to refresh every time the class changes */ + private static final long serialVersionUID = -8875782030758554999L; + + private X509Certificate mServerCert = null; + private String mHostInUrl; + + private CertificateExpiredException mCertificateExpiredException = null; + private CertificateNotYetValidException mCertificateNotYetValidException = null; + private CertPathValidatorException mCertPathValidatorException = null; + private CertificateException mOtherCertificateException = null; + private SSLPeerUnverifiedException mSslPeerUnverifiedException = null; + + public CertificateCombinedException(X509Certificate x509Certificate) { + mServerCert = x509Certificate; + } + + public X509Certificate getServerCertificate() { + return mServerCert; + } + + public String getHostInUrl() { + return mHostInUrl; + } + + public void setHostInUrl(String host) { + mHostInUrl = host; + } + + public CertificateExpiredException getCertificateExpiredException() { + return mCertificateExpiredException; + } + + public void setCertificateExpiredException(CertificateExpiredException c) { + mCertificateExpiredException = c; + } + + public CertificateNotYetValidException getCertificateNotYetValidException() { + return mCertificateNotYetValidException; + } + + public void setCertificateNotYetException(CertificateNotYetValidException c) { + mCertificateNotYetValidException = c; + } + + public CertPathValidatorException getCertPathValidatorException() { + return mCertPathValidatorException; + } + + public void setCertPathValidatorException(CertPathValidatorException c) { + mCertPathValidatorException = c; + } + + public CertificateException getOtherCertificateException() { + return mOtherCertificateException; + } + + public void setOtherCertificateException(CertificateException c) { + mOtherCertificateException = c; + } + + public SSLPeerUnverifiedException getSslPeerUnverifiedException() { + return mSslPeerUnverifiedException ; + } + + public void setSslPeerUnverifiedException(SSLPeerUnverifiedException s) { + mSslPeerUnverifiedException = s; + } + + public boolean isException() { + return (mCertificateExpiredException != null || + mCertificateNotYetValidException != null || + mCertPathValidatorException != null || + mOtherCertificateException != null || + mSslPeerUnverifiedException != null); + } + + public boolean isRecoverable() { + return (mCertificateExpiredException != null || + mCertificateNotYetValidException != null || + mCertPathValidatorException != null || + mSslPeerUnverifiedException != null); + } + +} diff --git a/src/com/owncloud/android/network/OwnCloudClientUtils.java b/src/com/owncloud/android/network/OwnCloudClientUtils.java new file mode 100644 index 00000000..bb299381 --- /dev/null +++ b/src/com/owncloud/android/network/OwnCloudClientUtils.java @@ -0,0 +1,285 @@ +/* ownCloud Android client application + * 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.network; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier; +import org.apache.http.conn.ssl.X509HostnameVerifier; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; +import com.owncloud.android.authentication.AccountAuthenticator; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.authentication.AccountUtils.AccountNotFoundException; + + +import eu.alefzero.webdav.WebdavClient; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.app.Activity; +import android.content.Context; +import android.net.Uri; +import android.os.Bundle; + +public class OwnCloudClientUtils { + + final private static String TAG = OwnCloudClientUtils.class.getSimpleName(); + + /** Default timeout for waiting data from the server */ + public static final int DEFAULT_DATA_TIMEOUT = 60000; + + /** Default timeout for establishing a connection */ + public static final int DEFAULT_CONNECTION_TIMEOUT = 60000; + + /** Connection manager for all the WebdavClients */ + private static MultiThreadedHttpConnectionManager mConnManager = null; + + private static Protocol mDefaultHttpsProtocol = null; + + private static AdvancedSslSocketFactory mAdvancedSslSocketFactory = null; + + private static X509HostnameVerifier mHostnameVerifier = null; + + + /** + * Creates a WebdavClient setup for an ownCloud account + * + * Do not call this method from the main thread. + * + * @param account The ownCloud account + * @param appContext Android application context + * @return A WebdavClient object ready to be used + * @throws AuthenticatorException If the authenticator failed to get the authorization token for the account. + * @throws OperationCanceledException If the authenticator operation was cancelled while getting the authorization token for the account. + * @throws IOException If there was some I/O error while getting the authorization token for the account. + * @throws AccountNotFoundException If 'account' is unknown for the AccountManager + */ + public static WebdavClient createOwnCloudClient (Account account, Context appContext) throws OperationCanceledException, AuthenticatorException, IOException, AccountNotFoundException { + //Log_OC.d(TAG, "Creating WebdavClient associated to " + account.name); + + Uri uri = Uri.parse(AccountUtils.constructFullURLForAccount(appContext, account)); + AccountManager am = AccountManager.get(appContext); + boolean isOauth2 = am.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null; // TODO avoid calling to getUserData here + boolean isSamlSso = am.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_SAML_WEB_SSO) != null; + WebdavClient client = createOwnCloudClient(uri, appContext, !isSamlSso); + if (isOauth2) { + String accessToken = am.blockingGetAuthToken(account, MainApp.getAuthTokenTypeAccessToken(), false); + client.setBearerCredentials(accessToken); // TODO not assume that the access token is a bearer token + + } else if (isSamlSso) { // TODO avoid a call to getUserData here + String accessToken = am.blockingGetAuthToken(account, MainApp.getAuthTokenTypeSamlSessionCookie(), false); + client.setSsoSessionCookie(accessToken); + + } else { + String username = account.name.substring(0, account.name.lastIndexOf('@')); + //String password = am.getPassword(account); + String password = am.blockingGetAuthToken(account, MainApp.getAuthTokenTypePass(), false); + client.setBasicCredentials(username, password); + } + + return client; + } + + + public static WebdavClient createOwnCloudClient (Account account, Context appContext, Activity currentActivity) throws OperationCanceledException, AuthenticatorException, IOException, AccountNotFoundException { + Uri uri = Uri.parse(AccountUtils.constructFullURLForAccount(appContext, account)); + AccountManager am = AccountManager.get(appContext); + boolean isOauth2 = am.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null; // TODO avoid calling to getUserData here + boolean isSamlSso = am.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_SAML_WEB_SSO) != null; + WebdavClient client = createOwnCloudClient(uri, appContext, !isSamlSso); + + if (isOauth2) { // TODO avoid a call to getUserData here + AccountManagerFuture future = am.getAuthToken(account, MainApp.getAuthTokenTypeAccessToken(), null, currentActivity, null, null); + Bundle result = future.getResult(); + String accessToken = result.getString(AccountManager.KEY_AUTHTOKEN); + if (accessToken == null) throw new AuthenticatorException("WTF!"); + client.setBearerCredentials(accessToken); // TODO not assume that the access token is a bearer token + + } else if (isSamlSso) { // TODO avoid a call to getUserData here + AccountManagerFuture future = am.getAuthToken(account, MainApp.getAuthTokenTypeSamlSessionCookie(), null, currentActivity, null, null); + Bundle result = future.getResult(); + String accessToken = result.getString(AccountManager.KEY_AUTHTOKEN); + if (accessToken == null) throw new AuthenticatorException("WTF!"); + client.setSsoSessionCookie(accessToken); + + } else { + String username = account.name.substring(0, account.name.lastIndexOf('@')); + //String password = am.getPassword(account); + //String password = am.blockingGetAuthToken(account, MainApp.getAuthTokenTypePass(), false); + AccountManagerFuture future = am.getAuthToken(account, MainApp.getAuthTokenTypePass(), null, currentActivity, null, null); + Bundle result = future.getResult(); + String password = result.getString(AccountManager.KEY_AUTHTOKEN); + client.setBasicCredentials(username, password); + } + + return client; + } + + /** + * Creates a WebdavClient to access a URL and sets the desired parameters for ownCloud client connections. + * + * @param uri URL to the ownCloud server + * @param context Android context where the WebdavClient is being created. + * @return A WebdavClient object ready to be used + */ + public static WebdavClient createOwnCloudClient(Uri uri, Context context, boolean followRedirects) { + try { + registerAdvancedSslContext(true, context); + } catch (GeneralSecurityException e) { + Log_OC.e(TAG, "Advanced SSL Context could not be loaded. Default SSL management in the system will be used for HTTPS connections", e); + + } catch (IOException e) { + Log_OC.e(TAG, "The local server truststore could not be read. Default SSL management in the system will be used for HTTPS connections", e); + } + + WebdavClient client = new WebdavClient(getMultiThreadedConnManager()); + + client.setDefaultTimeouts(DEFAULT_DATA_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT); + client.setBaseUri(uri); + client.setFollowRedirects(followRedirects); + + return client; + } + + + /** + * Registers or unregisters the proper components for advanced SSL handling. + * @throws IOException + */ + private static void registerAdvancedSslContext(boolean register, Context context) throws GeneralSecurityException, IOException { + Protocol pr = null; + try { + pr = Protocol.getProtocol("https"); + if (pr != null && mDefaultHttpsProtocol == null) { + mDefaultHttpsProtocol = pr; + } + } catch (IllegalStateException e) { + // nothing to do here; really + } + boolean isRegistered = (pr != null && pr.getSocketFactory() instanceof AdvancedSslSocketFactory); + if (register && !isRegistered) { + Protocol.registerProtocol("https", new Protocol("https", getAdvancedSslSocketFactory(context), 443)); + + } else if (!register && isRegistered) { + if (mDefaultHttpsProtocol != null) { + Protocol.registerProtocol("https", mDefaultHttpsProtocol); + } + } + } + + public static AdvancedSslSocketFactory getAdvancedSslSocketFactory(Context context) throws GeneralSecurityException, IOException { + if (mAdvancedSslSocketFactory == null) { + KeyStore trustStore = getKnownServersStore(context); + AdvancedX509TrustManager trustMgr = new AdvancedX509TrustManager(trustStore); + TrustManager[] tms = new TrustManager[] { trustMgr }; + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, tms, null); + + mHostnameVerifier = new BrowserCompatHostnameVerifier(); + mAdvancedSslSocketFactory = new AdvancedSslSocketFactory(sslContext, trustMgr, mHostnameVerifier); + } + return mAdvancedSslSocketFactory; + } + + + private static String LOCAL_TRUSTSTORE_FILENAME = "knownServers.bks"; + + private static String LOCAL_TRUSTSTORE_PASSWORD = "password"; + + private static KeyStore mKnownServersStore = null; + + /** + * Returns the local store of reliable server certificates, explicitly accepted by the user. + * + * Returns a KeyStore instance with empty content if the local store was never created. + * + * Loads the store from the storage environment if needed. + * + * @param context Android context where the operation is being performed. + * @return KeyStore instance with explicitly-accepted server certificates. + * @throws KeyStoreException When the KeyStore instance could not be created. + * @throws IOException When an existing local trust store could not be loaded. + * @throws NoSuchAlgorithmException When the existing local trust store was saved with an unsupported algorithm. + * @throws CertificateException When an exception occurred while loading the certificates from the local trust store. + */ + private static KeyStore getKnownServersStore(Context context) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + if (mKnownServersStore == null) { + //mKnownServersStore = KeyStore.getInstance("BKS"); + mKnownServersStore = KeyStore.getInstance(KeyStore.getDefaultType()); + File localTrustStoreFile = new File(context.getFilesDir(), LOCAL_TRUSTSTORE_FILENAME); + Log_OC.d(TAG, "Searching known-servers store at " + localTrustStoreFile.getAbsolutePath()); + if (localTrustStoreFile.exists()) { + InputStream in = new FileInputStream(localTrustStoreFile); + try { + mKnownServersStore.load(in, LOCAL_TRUSTSTORE_PASSWORD.toCharArray()); + } finally { + in.close(); + } + } else { + mKnownServersStore.load(null, LOCAL_TRUSTSTORE_PASSWORD.toCharArray()); // necessary to initialize an empty KeyStore instance + } + } + return mKnownServersStore; + } + + + public static void addCertToKnownServersStore(Certificate cert, Context context) throws KeyStoreException, NoSuchAlgorithmException, + CertificateException, IOException { + KeyStore knownServers = getKnownServersStore(context); + knownServers.setCertificateEntry(Integer.toString(cert.hashCode()), cert); + FileOutputStream fos = null; + try { + fos = context.openFileOutput(LOCAL_TRUSTSTORE_FILENAME, Context.MODE_PRIVATE); + knownServers.store(fos, LOCAL_TRUSTSTORE_PASSWORD.toCharArray()); + } finally { + fos.close(); + } + } + + + static private MultiThreadedHttpConnectionManager getMultiThreadedConnManager() { + if (mConnManager == null) { + mConnManager = new MultiThreadedHttpConnectionManager(); + mConnManager.getParams().setDefaultMaxConnectionsPerHost(5); + mConnManager.getParams().setMaxTotalConnections(5); + } + return mConnManager; + } + + +} diff --git a/src/com/owncloud/android/network/ProgressiveDataTransferer.java b/src/com/owncloud/android/network/ProgressiveDataTransferer.java new file mode 100644 index 00000000..c6fa545b --- /dev/null +++ b/src/com/owncloud/android/network/ProgressiveDataTransferer.java @@ -0,0 +1,32 @@ +/* ownCloud Android client application + * 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.network; + +import java.util.Collection; + +import eu.alefzero.webdav.OnDatatransferProgressListener; + +public interface ProgressiveDataTransferer { + + public void addDatatransferProgressListener (OnDatatransferProgressListener listener); + + public void addDatatransferProgressListeners(Collection listeners); + + public void removeDatatransferProgressListener(OnDatatransferProgressListener listener); + +} diff --git a/src/com/owncloud/android/operations/ChunkedUploadFileOperation.java b/src/com/owncloud/android/operations/ChunkedUploadFileOperation.java new file mode 100644 index 00000000..2649d197 --- /dev/null +++ b/src/com/owncloud/android/operations/ChunkedUploadFileOperation.java @@ -0,0 +1,97 @@ +/* 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.operations; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.util.Random; + +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.methods.PutMethod; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.network.ProgressiveDataTransferer; + + +import android.accounts.Account; + +import eu.alefzero.webdav.ChunkFromFileChannelRequestEntity; +import eu.alefzero.webdav.WebdavClient; +import eu.alefzero.webdav.WebdavUtils; + +public class ChunkedUploadFileOperation extends UploadFileOperation { + + private static final long CHUNK_SIZE = 1024000; + private static final String OC_CHUNKED_HEADER = "OC-Chunked"; + private static final String TAG = ChunkedUploadFileOperation.class.getSimpleName(); + + public ChunkedUploadFileOperation( Account account, + OCFile file, + boolean isInstant, + boolean forceOverwrite, + int localBehaviour) { + + super(account, file, isInstant, forceOverwrite, localBehaviour); + } + + @Override + protected int uploadFile(WebdavClient client) throws HttpException, IOException { + int status = -1; + + FileChannel channel = null; + RandomAccessFile raf = null; + try { + File file = new File(getStoragePath()); + raf = new RandomAccessFile(file, "r"); + channel = raf.getChannel(); + mEntity = new ChunkFromFileChannelRequestEntity(channel, getMimeType(), CHUNK_SIZE, file); + ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListeners(getDataTransferListeners()); + long offset = 0; + String uriPrefix = client.getBaseUri() + WebdavUtils.encodePath(getRemotePath()) + "-chunking-" + Math.abs((new Random()).nextInt(9000)+1000) + "-" ; + long chunkCount = (long) Math.ceil((double)file.length() / CHUNK_SIZE); + for (int chunkIndex = 0; chunkIndex < chunkCount ; chunkIndex++, offset += CHUNK_SIZE) { + if (mPutMethod != null) { + mPutMethod.releaseConnection(); // let the connection available for other methods + } + mPutMethod = new PutMethod(uriPrefix + chunkCount + "-" + chunkIndex); + mPutMethod.addRequestHeader(OC_CHUNKED_HEADER, OC_CHUNKED_HEADER); + ((ChunkFromFileChannelRequestEntity)mEntity).setOffset(offset); + mPutMethod.setRequestEntity(mEntity); + status = client.executeMethod(mPutMethod); + client.exhaustResponse(mPutMethod.getResponseBodyAsStream()); + Log_OC.d(TAG, "Upload of " + getStoragePath() + " to " + getRemotePath() + ", chunk index " + chunkIndex + ", count " + chunkCount + ", HTTP result status " + status); + if (!isSuccess(status)) + break; + } + + } finally { + if (channel != null) + channel.close(); + if (raf != null) + raf.close(); + if (mPutMethod != null) + mPutMethod.releaseConnection(); // let the connection available for other methods + } + return status; + } + +} diff --git a/src/com/owncloud/android/operations/CreateFolderOperation.java b/src/com/owncloud/android/operations/CreateFolderOperation.java new file mode 100644 index 00000000..b3456ee2 --- /dev/null +++ b/src/com/owncloud/android/operations/CreateFolderOperation.java @@ -0,0 +1,118 @@ +/* ownCloud Android client application + * Copyright (C) 2012 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.operations; + +import java.io.File; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.jackrabbit.webdav.client.methods.MkColMethod; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.OCFile; + + +import eu.alefzero.webdav.WebdavClient; +import eu.alefzero.webdav.WebdavUtils; + +/** + * Remote operation performing the creation of a new folder in the ownCloud server. + * + * @author David A. Velasco + */ +public class CreateFolderOperation extends RemoteOperation { + + private static final String TAG = CreateFolderOperation.class.getSimpleName(); + + private static final int READ_TIMEOUT = 10000; + private static final int CONNECTION_TIMEOUT = 5000; + + protected String mRemotePath; + protected boolean mCreateFullPath; + protected DataStorageManager mStorageManager; + + /** + * Constructor + * + * @param remotePath Full path to the new directory to create in the remote server. + * @param createFullPath 'True' means that all the ancestor folders should be created if don't exist yet. + * @param storageManager Reference to the local database corresponding to the account where the file is contained. + */ + public CreateFolderOperation(String remotePath, boolean createFullPath, DataStorageManager storageManager) { + mRemotePath = remotePath; + mCreateFullPath = createFullPath; + mStorageManager = storageManager; + } + + + /** + * Performs the operation + * + * @param client Client object to communicate with the remote ownCloud server. + */ + @Override + protected RemoteOperationResult run(WebdavClient client) { + RemoteOperationResult result = null; + MkColMethod mkcol = null; + try { + mkcol = new MkColMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath)); + int status = client.executeMethod(mkcol, READ_TIMEOUT, CONNECTION_TIMEOUT); + if (!mkcol.succeeded() && mkcol.getStatusCode() == HttpStatus.SC_CONFLICT && mCreateFullPath) { + result = createParentFolder(getParentPath(), client); + status = client.executeMethod(mkcol, READ_TIMEOUT, CONNECTION_TIMEOUT); // second (and last) try + } + if (mkcol.succeeded()) { + // Save new directory in local database + OCFile newDir = new OCFile(mRemotePath); + newDir.setMimetype("DIR"); + long parentId = mStorageManager.getFileByPath(getParentPath()).getFileId(); + newDir.setParentId(parentId); + newDir.setModificationTimestamp(System.currentTimeMillis()); + mStorageManager.saveFile(newDir); + } + result = new RemoteOperationResult(mkcol.succeeded(), status, mkcol.getResponseHeaders()); + Log_OC.d(TAG, "Create directory " + mRemotePath + ": " + result.getLogMessage()); + client.exhaustResponse(mkcol.getResponseBodyAsStream()); + + } catch (Exception e) { + result = new RemoteOperationResult(e); + Log_OC.e(TAG, "Create directory " + mRemotePath + ": " + result.getLogMessage(), e); + + } finally { + if (mkcol != null) + mkcol.releaseConnection(); + } + return result; + } + + + private String getParentPath() { + String parentPath = new File(mRemotePath).getParent(); + parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR; + return parentPath; + } + + + private RemoteOperationResult createParentFolder(String parentPath, WebdavClient client) { + RemoteOperation operation = new CreateFolderOperation( parentPath, + mCreateFullPath, + mStorageManager ); + return operation.execute(client); + } + +} diff --git a/src/com/owncloud/android/operations/DownloadFileOperation.java b/src/com/owncloud/android/operations/DownloadFileOperation.java new file mode 100644 index 00000000..db986f47 --- /dev/null +++ b/src/com/owncloud/android/operations/DownloadFileOperation.java @@ -0,0 +1,236 @@ +/* ownCloud Android client application + * 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.operations; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.http.HttpStatus; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.operations.RemoteOperation; +import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.utils.FileStorageUtils; + + +import eu.alefzero.webdav.OnDatatransferProgressListener; +import eu.alefzero.webdav.WebdavClient; +import eu.alefzero.webdav.WebdavUtils; +import android.accounts.Account; +import android.webkit.MimeTypeMap; + +/** + * Remote operation performing the download of a file to an ownCloud server + * + * @author David A. Velasco + */ +public class DownloadFileOperation extends RemoteOperation { + + private static final String TAG = DownloadFileOperation.class.getSimpleName(); + + private Account mAccount; + private OCFile mFile; + private Set mDataTransferListeners = new HashSet(); + private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false); + private long mModificationTimestamp = 0; + private GetMethod mGet; + + + public DownloadFileOperation(Account account, OCFile file) { + if (account == null) + throw new IllegalArgumentException("Illegal null account in DownloadFileOperation creation"); + if (file == null) + throw new IllegalArgumentException("Illegal null file in DownloadFileOperation creation"); + + mAccount = account; + mFile = file; + } + + + public Account getAccount() { + return mAccount; + } + + public OCFile getFile() { + return mFile; + } + + public String getSavePath() { + String path = mFile.getStoragePath(); // re-downloads should be done over the original file + if (path != null && path.length() > 0) { + return path; + } + return FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); + } + + public String getTmpPath() { + return FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath(); + } + + public String getRemotePath() { + return mFile.getRemotePath(); + } + + public String getMimeType() { + String mimeType = mFile.getMimetype(); + if (mimeType == null || mimeType.length() <= 0) { + try { + mimeType = MimeTypeMap.getSingleton() + .getMimeTypeFromExtension( + mFile.getRemotePath().substring(mFile.getRemotePath().lastIndexOf('.') + 1)); + } catch (IndexOutOfBoundsException e) { + Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + mFile.getRemotePath()); + } + } + if (mimeType == null) { + mimeType = "application/octet-stream"; + } + return mimeType; + } + + public long getSize() { + return mFile.getFileLength(); + } + + public long getModificationTimestamp() { + return (mModificationTimestamp > 0) ? mModificationTimestamp : mFile.getModificationTimestamp(); + } + + + public void addDatatransferProgressListener (OnDatatransferProgressListener listener) { + synchronized (mDataTransferListeners) { + mDataTransferListeners.add(listener); + } + } + + public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) { + synchronized (mDataTransferListeners) { + mDataTransferListeners.remove(listener); + } + } + + @Override + protected RemoteOperationResult run(WebdavClient client) { + RemoteOperationResult result = null; + File newFile = null; + boolean moved = true; + + /// download will be performed to a temporal file, then moved to the final location + File tmpFile = new File(getTmpPath()); + + /// perform the download + try { + tmpFile.getParentFile().mkdirs(); + int status = downloadFile(client, tmpFile); + if (isSuccess(status)) { + newFile = new File(getSavePath()); + newFile.getParentFile().mkdirs(); + moved = tmpFile.renameTo(newFile); + } + if (!moved) + result = new RemoteOperationResult(RemoteOperationResult.ResultCode.LOCAL_STORAGE_NOT_MOVED); + else + result = new RemoteOperationResult(isSuccess(status), status, (mGet != null ? mGet.getResponseHeaders() : null)); + Log_OC.i(TAG, "Download of " + mFile.getRemotePath() + " to " + getSavePath() + ": " + result.getLogMessage()); + + } catch (Exception e) { + result = new RemoteOperationResult(e); + Log_OC.e(TAG, "Download of " + mFile.getRemotePath() + " to " + getSavePath() + ": " + result.getLogMessage(), e); + } + + return result; + } + + + public boolean isSuccess(int status) { + return (status == HttpStatus.SC_OK); + } + + + protected int downloadFile(WebdavClient client, File targetFile) throws HttpException, IOException, OperationCancelledException { + int status = -1; + boolean savedFile = false; + mGet = new GetMethod(client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath())); + Iterator it = null; + + FileOutputStream fos = null; + try { + status = client.executeMethod(mGet); + if (isSuccess(status)) { + targetFile.createNewFile(); + BufferedInputStream bis = new BufferedInputStream(mGet.getResponseBodyAsStream()); + fos = new FileOutputStream(targetFile); + long transferred = 0; + + byte[] bytes = new byte[4096]; + int readResult = 0; + while ((readResult = bis.read(bytes)) != -1) { + synchronized(mCancellationRequested) { + if (mCancellationRequested.get()) { + mGet.abort(); + throw new OperationCancelledException(); + } + } + fos.write(bytes, 0, readResult); + transferred += readResult; + synchronized (mDataTransferListeners) { + it = mDataTransferListeners.iterator(); + while (it.hasNext()) { + it.next().onTransferProgress(readResult, transferred, mFile.getFileLength(), targetFile.getName()); + } + } + } + savedFile = true; + Header modificationTime = mGet.getResponseHeader("Last-Modified"); + if (modificationTime != null) { + Date d = WebdavUtils.parseResponseDate((String) modificationTime.getValue()); + mModificationTimestamp = (d != null) ? d.getTime() : 0; + } + + } else { + client.exhaustResponse(mGet.getResponseBodyAsStream()); + } + + } finally { + if (fos != null) fos.close(); + if (!savedFile && targetFile.exists()) { + targetFile.delete(); + } + mGet.releaseConnection(); // let the connection available for other methods + } + return status; + } + + + public void cancel() { + mCancellationRequested.set(true); // atomic set; there is no need of synchronizing it + } + + +} diff --git a/src/com/owncloud/android/operations/ExistenceCheckOperation.java b/src/com/owncloud/android/operations/ExistenceCheckOperation.java new file mode 100644 index 00000000..24336bd6 --- /dev/null +++ b/src/com/owncloud/android/operations/ExistenceCheckOperation.java @@ -0,0 +1,96 @@ +/* ownCloud Android client application + * Copyright (C) 2012 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.operations; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.HeadMethod; + +import com.owncloud.android.Log_OC; + + +import eu.alefzero.webdav.WebdavClient; +import eu.alefzero.webdav.WebdavUtils; +import android.content.Context; +import android.net.ConnectivityManager; + +/** + * Operation to check the existence or absence of a path in a remote server. + * + * @author David A. Velasco + */ +public class ExistenceCheckOperation extends RemoteOperation { + + /** Maximum time to wait for a response from the server in MILLISECONDs. */ + public static final int TIMEOUT = 10000; + + private static final String TAG = ExistenceCheckOperation.class.getSimpleName(); + + private String mPath; + private Context mContext; + private boolean mSuccessIfAbsent; + + + /** + * Full constructor. Success of the operation will depend upon the value of successIfAbsent. + * + * @param path Path to append to the URL owned by the client instance. + * @param context Android application context. + * @param successIfAbsent When 'true', the operation finishes in success if the path does NOT exist in the remote server (HTTP 404). + */ + public ExistenceCheckOperation(String path, Context context, boolean successIfAbsent) { + mPath = (path != null) ? path : ""; + mContext = context; + mSuccessIfAbsent = successIfAbsent; + } + + + @Override + protected RemoteOperationResult run(WebdavClient client) { + if (!isOnline()) { + return new RemoteOperationResult(RemoteOperationResult.ResultCode.NO_NETWORK_CONNECTION); + } + RemoteOperationResult result = null; + HeadMethod head = null; + try { + head = new HeadMethod(client.getBaseUri() + WebdavUtils.encodePath(mPath)); + int status = client.executeMethod(head, TIMEOUT, TIMEOUT); + client.exhaustResponse(head.getResponseBodyAsStream()); + boolean success = (status == HttpStatus.SC_OK && !mSuccessIfAbsent) || (status == HttpStatus.SC_NOT_FOUND && mSuccessIfAbsent); + result = new RemoteOperationResult(success, status, head.getResponseHeaders()); + Log_OC.d(TAG, "Existence check for " + client.getBaseUri() + WebdavUtils.encodePath(mPath) + " targeting for " + (mSuccessIfAbsent ? " absence " : " existence ") + "finished with HTTP status " + status + (!success?"(FAIL)":"")); + + } catch (Exception e) { + result = new RemoteOperationResult(e); + Log_OC.e(TAG, "Existence check for " + client.getBaseUri() + WebdavUtils.encodePath(mPath) + " targeting for " + (mSuccessIfAbsent ? " absence " : " existence ") + ": " + result.getLogMessage(), result.getException()); + + } finally { + if (head != null) + head.releaseConnection(); + } + return result; + } + + private boolean isOnline() { + ConnectivityManager cm = (ConnectivityManager) mContext + .getSystemService(Context.CONNECTIVITY_SERVICE); + return cm != null && cm.getActiveNetworkInfo() != null + && cm.getActiveNetworkInfo().isConnectedOrConnecting(); + } + + +} diff --git a/src/com/owncloud/android/operations/OAuth2GetAccessToken.java b/src/com/owncloud/android/operations/OAuth2GetAccessToken.java new file mode 100644 index 00000000..09a6e1e5 --- /dev/null +++ b/src/com/owncloud/android/operations/OAuth2GetAccessToken.java @@ -0,0 +1,175 @@ +package com.owncloud.android.operations; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.NameValuePair; +import org.json.JSONException; +import org.json.JSONObject; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.authentication.OAuth2Constants; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; + + +import eu.alefzero.webdav.WebdavClient; + +public class OAuth2GetAccessToken extends RemoteOperation { + + private static final String TAG = OAuth2GetAccessToken.class.getSimpleName(); + + private String mClientId; + private String mRedirectUri; + private String mGrantType; + + private String mOAuth2AuthorizationResponse; + private Map mOAuth2ParsedAuthorizationResponse; + private Map mResultTokenMap; + + + public OAuth2GetAccessToken(String clientId, String redirectUri, String grantType, String oAuth2AuthorizationResponse) { + mClientId = clientId; + mRedirectUri = redirectUri; + mGrantType = grantType; + mOAuth2AuthorizationResponse = oAuth2AuthorizationResponse; + mOAuth2ParsedAuthorizationResponse = new HashMap(); + mResultTokenMap = null; + } + + + public Map getOauth2AutorizationResponse() { + return mOAuth2ParsedAuthorizationResponse; + } + + public Map getResultTokenMap() { + return mResultTokenMap; + } + + @Override + protected RemoteOperationResult run(WebdavClient client) { + RemoteOperationResult result = null; + PostMethod postMethod = null; + + try { + parseAuthorizationResponse(); + if (mOAuth2ParsedAuthorizationResponse.keySet().contains(OAuth2Constants.KEY_ERROR)) { + if (OAuth2Constants.VALUE_ERROR_ACCESS_DENIED.equals(mOAuth2ParsedAuthorizationResponse.get(OAuth2Constants.KEY_ERROR))) { + result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR_ACCESS_DENIED); + } else { + result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); + } + } + + if (result == null) { + NameValuePair[] nameValuePairs = new NameValuePair[4]; + nameValuePairs[0] = new NameValuePair(OAuth2Constants.KEY_GRANT_TYPE, mGrantType); + nameValuePairs[1] = new NameValuePair(OAuth2Constants.KEY_CODE, mOAuth2ParsedAuthorizationResponse.get(OAuth2Constants.KEY_CODE)); + nameValuePairs[2] = new NameValuePair(OAuth2Constants.KEY_REDIRECT_URI, mRedirectUri); + nameValuePairs[3] = new NameValuePair(OAuth2Constants.KEY_CLIENT_ID, mClientId); + //nameValuePairs[4] = new NameValuePair(OAuth2Constants.KEY_SCOPE, mOAuth2ParsedAuthorizationResponse.get(OAuth2Constants.KEY_SCOPE)); + + postMethod = new PostMethod(client.getBaseUri().toString()); + postMethod.setRequestBody(nameValuePairs); + int status = client.executeMethod(postMethod); + + String response = postMethod.getResponseBodyAsString(); + if (response != null && response.length() > 0) { + JSONObject tokenJson = new JSONObject(response); + parseAccessTokenResult(tokenJson); + if (mResultTokenMap.get(OAuth2Constants.KEY_ERROR) != null || mResultTokenMap.get(OAuth2Constants.KEY_ACCESS_TOKEN) == null) { + result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); + + } else { + result = new RemoteOperationResult(true, status, postMethod.getResponseHeaders()); + } + + } else { + client.exhaustResponse(postMethod.getResponseBodyAsStream()); + result = new RemoteOperationResult(false, status, postMethod.getResponseHeaders()); + } + } + + } catch (Exception e) { + result = new RemoteOperationResult(e); + + } finally { + if (postMethod != null) + postMethod.releaseConnection(); // let the connection available for other methods + + if (result.isSuccess()) { + Log_OC.i(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage()); + + } else if (result.getException() != null) { + Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage(), result.getException()); + + } else if (result.getCode() == ResultCode.OAUTH2_ERROR) { + Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + ((mResultTokenMap != null) ? mResultTokenMap.get(OAuth2Constants.KEY_ERROR) : "NULL")); + + } else { + Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage()); + } + } + + return result; + } + + + private void parseAuthorizationResponse() { + String[] pairs = mOAuth2AuthorizationResponse.split("&"); + int i = 0; + String key = ""; + String value = ""; + StringBuilder sb = new StringBuilder(); + while (pairs.length > i) { + int j = 0; + String[] part = pairs[i].split("="); + while (part.length > j) { + String p = part[j]; + if (j == 0) { + key = p; + sb.append(key + " = "); + } else if (j == 1) { + value = p; + mOAuth2ParsedAuthorizationResponse.put(key, value); + sb.append(value + "\n"); + } + + Log_OC.v(TAG, "[" + i + "," + j + "] = " + p); + j++; + } + i++; + } + } + + + private void parseAccessTokenResult (JSONObject tokenJson) throws JSONException { + mResultTokenMap = new HashMap(); + + if (tokenJson.has(OAuth2Constants.KEY_ACCESS_TOKEN)) { + mResultTokenMap.put(OAuth2Constants.KEY_ACCESS_TOKEN, tokenJson.getString(OAuth2Constants.KEY_ACCESS_TOKEN)); + } + if (tokenJson.has(OAuth2Constants.KEY_TOKEN_TYPE)) { + mResultTokenMap.put(OAuth2Constants.KEY_TOKEN_TYPE, tokenJson.getString(OAuth2Constants.KEY_TOKEN_TYPE)); + } + if (tokenJson.has(OAuth2Constants.KEY_EXPIRES_IN)) { + mResultTokenMap.put(OAuth2Constants.KEY_EXPIRES_IN, tokenJson.getString(OAuth2Constants.KEY_EXPIRES_IN)); + } + if (tokenJson.has(OAuth2Constants.KEY_REFRESH_TOKEN)) { + mResultTokenMap.put(OAuth2Constants.KEY_REFRESH_TOKEN, tokenJson.getString(OAuth2Constants.KEY_REFRESH_TOKEN)); + } + if (tokenJson.has(OAuth2Constants.KEY_SCOPE)) { + mResultTokenMap.put(OAuth2Constants.KEY_SCOPE, tokenJson.getString(OAuth2Constants.KEY_SCOPE)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR, tokenJson.getString(OAuth2Constants.KEY_ERROR)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR_DESCRIPTION)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR_DESCRIPTION, tokenJson.getString(OAuth2Constants.KEY_ERROR_DESCRIPTION)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR_URI)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR_URI, tokenJson.getString(OAuth2Constants.KEY_ERROR_URI)); + } + } + +} diff --git a/src/com/owncloud/android/operations/OnRemoteOperationListener.java b/src/com/owncloud/android/operations/OnRemoteOperationListener.java new file mode 100644 index 00000000..e6a58e73 --- /dev/null +++ b/src/com/owncloud/android/operations/OnRemoteOperationListener.java @@ -0,0 +1,25 @@ +/* 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.operations; + +public interface OnRemoteOperationListener { + + void onRemoteOperationFinish(RemoteOperation caller, RemoteOperationResult result); + +} diff --git a/src/com/owncloud/android/operations/OperationCancelledException.java b/src/com/owncloud/android/operations/OperationCancelledException.java new file mode 100644 index 00000000..0b7878ce --- /dev/null +++ b/src/com/owncloud/android/operations/OperationCancelledException.java @@ -0,0 +1,28 @@ +/* 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.operations; + +public class OperationCancelledException extends Exception { + + /** + * Generated serial version - to avoid Java warning + */ + private static final long serialVersionUID = -6350981497740424983L; + +} diff --git a/src/com/owncloud/android/operations/OwnCloudServerCheckOperation.java b/src/com/owncloud/android/operations/OwnCloudServerCheckOperation.java new file mode 100644 index 00000000..62342249 --- /dev/null +++ b/src/com/owncloud/android/operations/OwnCloudServerCheckOperation.java @@ -0,0 +1,138 @@ +/* ownCloud Android client application + * 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.operations; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.GetMethod; +import org.json.JSONException; +import org.json.JSONObject; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.utils.OwnCloudVersion; + + +import eu.alefzero.webdav.WebdavClient; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.Uri; + +public class OwnCloudServerCheckOperation extends RemoteOperation { + + /** Maximum time to wait for a response from the server when the connection is being tested, in MILLISECONDs. */ + public static final int TRY_CONNECTION_TIMEOUT = 5000; + + private static final String TAG = OwnCloudServerCheckOperation.class.getSimpleName(); + + private String mUrl; + private RemoteOperationResult mLatestResult; + private Context mContext; + private OwnCloudVersion mOCVersion; + + public OwnCloudServerCheckOperation(String url, Context context) { + mUrl = url; + mContext = context; + mOCVersion = null; + } + + public OwnCloudVersion getDiscoveredVersion() { + return mOCVersion; + } + + private boolean tryConnection(WebdavClient wc, String urlSt) { + boolean retval = false; + GetMethod get = null; + try { + get = new GetMethod(urlSt); + int status = wc.executeMethod(get, TRY_CONNECTION_TIMEOUT, TRY_CONNECTION_TIMEOUT); + String response = get.getResponseBodyAsString(); + if (status == HttpStatus.SC_OK) { + JSONObject json = new JSONObject(response); + if (!json.getBoolean("installed")) { + mLatestResult = new RemoteOperationResult(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED); + } else { + mOCVersion = new OwnCloudVersion(json.getString("version")); + if (!mOCVersion.isVersionValid()) { + mLatestResult = new RemoteOperationResult(RemoteOperationResult.ResultCode.BAD_OC_VERSION); + + } else { + mLatestResult = new RemoteOperationResult(urlSt.startsWith("https://") ? + RemoteOperationResult.ResultCode.OK_SSL : + RemoteOperationResult.ResultCode.OK_NO_SSL + ); + + retval = true; + } + } + + } else { + mLatestResult = new RemoteOperationResult(false, status, get.getResponseHeaders()); + } + + } catch (JSONException e) { + mLatestResult = new RemoteOperationResult(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED); + + } catch (Exception e) { + mLatestResult = new RemoteOperationResult(e); + + } finally { + if (get != null) + get.releaseConnection(); + } + + if (mLatestResult.isSuccess()) { + Log_OC.i(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage()); + + } else if (mLatestResult.getException() != null) { + Log_OC.e(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage(), mLatestResult.getException()); + + } else { + Log_OC.e(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage()); + } + + return retval; + } + + private boolean isOnline() { + ConnectivityManager cm = (ConnectivityManager) mContext + .getSystemService(Context.CONNECTIVITY_SERVICE); + return cm != null && cm.getActiveNetworkInfo() != null + && cm.getActiveNetworkInfo().isConnectedOrConnecting(); + } + + @Override + protected RemoteOperationResult run(WebdavClient client) { + if (!isOnline()) { + return new RemoteOperationResult(RemoteOperationResult.ResultCode.NO_NETWORK_CONNECTION); + } + if (mUrl.startsWith("http://") || mUrl.startsWith("https://")) { + tryConnection(client, mUrl + AccountUtils.STATUS_PATH); + + } else { + client.setBaseUri(Uri.parse("https://" + mUrl + AccountUtils.STATUS_PATH)); + boolean httpsSuccess = tryConnection(client, "https://" + mUrl + AccountUtils.STATUS_PATH); + if (!httpsSuccess && !mLatestResult.isSslRecoverableException()) { + Log_OC.d(TAG, "establishing secure connection failed, trying non secure connection"); + client.setBaseUri(Uri.parse("http://" + mUrl + AccountUtils.STATUS_PATH)); + tryConnection(client, "http://" + mUrl + AccountUtils.STATUS_PATH); + } + } + return mLatestResult; + } + +} diff --git a/src/com/owncloud/android/operations/RemoteOperation.java b/src/com/owncloud/android/operations/RemoteOperation.java new file mode 100644 index 00000000..fbb93e61 --- /dev/null +++ b/src/com/owncloud/android/operations/RemoteOperation.java @@ -0,0 +1,293 @@ +/* ownCloud Android client application + * 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.operations; + +import java.io.IOException; + +import org.apache.commons.httpclient.Credentials; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; +import com.owncloud.android.network.BearerCredentials; +import com.owncloud.android.network.OwnCloudClientUtils; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; + + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountsException; +import android.app.Activity; +import android.content.Context; +import android.os.Handler; + +import eu.alefzero.webdav.WebdavClient; + +/** + * Operation which execution involves one or several interactions with an ownCloud server. + * + * Provides methods to execute the operation both synchronously or asynchronously. + * + * @author David A. Velasco + */ +public abstract class RemoteOperation implements Runnable { + + private static final String TAG = RemoteOperation.class.getSimpleName(); + + /** ownCloud account in the remote ownCloud server to operate */ + private Account mAccount = null; + + /** Android Application context */ + private Context mContext = null; + + /** Object to interact with the remote server */ + private WebdavClient mClient = null; + + /** Callback object to notify about the execution of the remote operation */ + private OnRemoteOperationListener mListener = null; + + /** Handler to the thread where mListener methods will be called */ + private Handler mListenerHandler = null; + + /** Activity */ + private Activity mCallerActivity; + + + /** + * Abstract method to implement the operation in derived classes. + */ + protected abstract RemoteOperationResult run(WebdavClient client); + + + /** + * Synchronously executes the remote operation on the received ownCloud account. + * + * Do not call this method from the main thread. + * + * This method should be used whenever an ownCloud account is available, instead of {@link #execute(WebdavClient)}. + * + * @param account ownCloud account in remote ownCloud server to reach during the execution of the operation. + * @param context Android context for the component calling the method. + * @return Result of the operation. + */ + public final RemoteOperationResult execute(Account account, Context context) { + if (account == null) + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Account"); + if (context == null) + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Context"); + mAccount = account; + mContext = context.getApplicationContext(); + try { + mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext); + } catch (Exception e) { + Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, e); + return new RemoteOperationResult(e); + } + return run(mClient); + } + + + /** + * Synchronously executes the remote operation + * + * Do not call this method from the main thread. + * + * @param client Client object to reach an ownCloud server during the execution of the operation. + * @return Result of the operation. + */ + public final RemoteOperationResult execute(WebdavClient client) { + if (client == null) + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient"); + mClient = client; + return run(client); + } + + + /** + * Asynchronously executes the remote operation + * + * This method should be used whenever an ownCloud account is available, instead of {@link #execute(WebdavClient)}. + * + * @param account ownCloud account in remote ownCloud server to reach during the execution of the operation. + * @param context Android context for the component calling the method. + * @param listener Listener to be notified about the execution of the operation. + * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. + * @return Thread were the remote operation is executed. + */ + public final Thread execute(Account account, Context context, OnRemoteOperationListener listener, Handler listenerHandler, Activity callerActivity) { + if (account == null) + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Account"); + if (context == null) + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Context"); + mAccount = account; + mContext = context.getApplicationContext(); + mCallerActivity = callerActivity; + mClient = null; // the client instance will be created from mAccount and mContext in the runnerThread to create below + + if (listener == null) { + throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a listener to notiy the result"); + } + mListener = listener; + + if (listenerHandler == null) { + throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a handler to the listener's thread"); + } + mListenerHandler = listenerHandler; + + Thread runnerThread = new Thread(this); + runnerThread.start(); + return runnerThread; + } + + + /** + * Asynchronously executes the remote operation + * + * @param client Client object to reach an ownCloud server during the execution of the operation. + * @param listener Listener to be notified about the execution of the operation. + * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. + * @return Thread were the remote operation is executed. + */ + public final Thread execute(WebdavClient client, OnRemoteOperationListener listener, Handler listenerHandler) { + if (client == null) { + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient"); + } + mClient = client; + + if (listener == null) { + throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a listener to notiy the result"); + } + mListener = listener; + + if (listenerHandler == null) { + throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a handler to the listener's thread"); + } + mListenerHandler = listenerHandler; + + Thread runnerThread = new Thread(this); + runnerThread.start(); + return runnerThread; + } + + /** + * Synchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient)} + * + * @param listener Listener to be notified about the execution of the operation. + * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. + * @return Thread were the remote operation is executed. + */ + public final RemoteOperationResult retry() { + return execute(mClient); + } + + /** + * Asynchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)} + * + * @param listener Listener to be notified about the execution of the operation. + * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. + * @return Thread were the remote operation is executed. + */ + public final Thread retry(OnRemoteOperationListener listener, Handler listenerHandler) { + return execute(mClient, listener, listenerHandler); + } + + + /** + * Asynchronous execution of the operation + * started by {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)}, + * and result posting. + * + * TODO refactor && clean the code; now it's a mess + */ + @Override + public final void run() { + RemoteOperationResult result = null; + boolean repeat = false; + do { + try{ + if (mClient == null) { + if (mAccount != null && mContext != null) { + if (mCallerActivity != null) { + mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext, mCallerActivity); + } else { + mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext); + } + } else { + throw new IllegalStateException("Trying to run a remote operation asynchronously with no client instance or account"); + } + } + + } catch (IOException e) { + Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, new AccountsException("I/O exception while trying to authorize the account", e)); + result = new RemoteOperationResult(e); + + } catch (AccountsException e) { + Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, e); + result = new RemoteOperationResult(e); + } + + if (result == null) + result = run(mClient); + + repeat = false; + if (mCallerActivity != null && mAccount != null && mContext != null && !result.isSuccess() && +// (result.getCode() == ResultCode.UNAUTHORIZED || (result.isTemporalRedirection() && result.isIdPRedirection()))) { + (result.getCode() == ResultCode.UNAUTHORIZED || result.isIdPRedirection())) { + /// possible fail due to lack of authorization in an operation performed in foreground + Credentials cred = mClient.getCredentials(); + String ssoSessionCookie = mClient.getSsoSessionCookie(); + if (cred != null || ssoSessionCookie != null) { + /// confirmed : unauthorized operation + AccountManager am = AccountManager.get(mContext); + boolean bearerAuthorization = (cred != null && cred instanceof BearerCredentials); + boolean samlBasedSsoAuthorization = (cred == null && ssoSessionCookie != null); + if (bearerAuthorization) { + am.invalidateAuthToken(MainApp.getAccountType(), ((BearerCredentials)cred).getAccessToken()); + } else if (samlBasedSsoAuthorization ) { + am.invalidateAuthToken(MainApp.getAccountType(), ssoSessionCookie); + } else { + am.clearPassword(mAccount); + } + mClient = null; + repeat = true; // when repeated, the creation of a new OwnCloudClient after erasing the saved credentials will trigger the login activity + result = null; + } + } + } while (repeat); + + final RemoteOperationResult resultToSend = result; + if (mListenerHandler != null && mListener != null) { + mListenerHandler.post(new Runnable() { + @Override + public void run() { + mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend); + } + }); + } + } + + + /** + * Returns the current client instance to access the remote server. + * + * @return Current client instance to access the remote server. + */ + public final WebdavClient getClient() { + return mClient; + } + + +} diff --git a/src/com/owncloud/android/operations/RemoteOperationResult.java b/src/com/owncloud/android/operations/RemoteOperationResult.java new file mode 100644 index 00000000..8d9b3ee2 --- /dev/null +++ b/src/com/owncloud/android/operations/RemoteOperationResult.java @@ -0,0 +1,338 @@ +/* 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.operations; + +import java.io.IOException; +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; + +import javax.net.ssl.SSLException; + +import org.apache.commons.httpclient.ConnectTimeoutException; +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.jackrabbit.webdav.DavException; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.authentication.AccountUtils.AccountNotFoundException; +import com.owncloud.android.network.CertificateCombinedException; + +import android.accounts.Account; +import android.accounts.AccountsException; + + +/** + * The result of a remote operation required to an ownCloud server. + * + * Provides a common classification of remote operation results for all the + * application. + * + * @author David A. Velasco + */ +public class RemoteOperationResult implements Serializable { + + /** Generated - should be refreshed every time the class changes!! */ + private static final long serialVersionUID = -4415103901492836870L; + + + + private static final String TAG = "RemoteOperationResult"; + + public enum ResultCode { + OK, + OK_SSL, + OK_NO_SSL, + UNHANDLED_HTTP_CODE, + UNAUTHORIZED, + FILE_NOT_FOUND, + INSTANCE_NOT_CONFIGURED, + UNKNOWN_ERROR, + WRONG_CONNECTION, + TIMEOUT, + INCORRECT_ADDRESS, + HOST_NOT_AVAILABLE, + NO_NETWORK_CONNECTION, + SSL_ERROR, + SSL_RECOVERABLE_PEER_UNVERIFIED, + BAD_OC_VERSION, + CANCELLED, + INVALID_LOCAL_FILE_NAME, + INVALID_OVERWRITE, + CONFLICT, + OAUTH2_ERROR, + SYNC_CONFLICT, + LOCAL_STORAGE_FULL, + LOCAL_STORAGE_NOT_MOVED, + LOCAL_STORAGE_NOT_COPIED, + OAUTH2_ERROR_ACCESS_DENIED, + QUOTA_EXCEEDED, + ACCOUNT_NOT_FOUND, + ACCOUNT_EXCEPTION, + ACCOUNT_NOT_NEW, + ACCOUNT_NOT_THE_SAME + } + + private boolean mSuccess = false; + private int mHttpCode = -1; + private Exception mException = null; + private ResultCode mCode = ResultCode.UNKNOWN_ERROR; + private String mRedirectedLocation; + + public RemoteOperationResult(ResultCode code) { + mCode = code; + mSuccess = (code == ResultCode.OK || code == ResultCode.OK_SSL || code == ResultCode.OK_NO_SSL); + } + + private RemoteOperationResult(boolean success, int httpCode) { + mSuccess = success; + mHttpCode = httpCode; + + if (success) { + mCode = ResultCode.OK; + + } else if (httpCode > 0) { + switch (httpCode) { + case HttpStatus.SC_UNAUTHORIZED: + mCode = ResultCode.UNAUTHORIZED; + break; + case HttpStatus.SC_NOT_FOUND: + mCode = ResultCode.FILE_NOT_FOUND; + break; + case HttpStatus.SC_INTERNAL_SERVER_ERROR: + mCode = ResultCode.INSTANCE_NOT_CONFIGURED; + break; + case HttpStatus.SC_CONFLICT: + mCode = ResultCode.CONFLICT; + break; + case HttpStatus.SC_INSUFFICIENT_STORAGE: + mCode = ResultCode.QUOTA_EXCEEDED; + break; + default: + mCode = ResultCode.UNHANDLED_HTTP_CODE; + Log_OC.d(TAG, "RemoteOperationResult has processed UNHANDLED_HTTP_CODE: " + httpCode); + } + } + } + + public RemoteOperationResult(boolean success, int httpCode, Header[] headers) { + this(success, httpCode); + if (headers != null) { + Header current; + for (int i=0; i= HttpStatus.SC_INTERNAL_SERVER_ERROR); + } + + public boolean isException() { + return (mException != null); + } + + public boolean isTemporalRedirection() { + return (mHttpCode == 302 || mHttpCode == 307); + } + + public String getRedirectedLocation() { + return mRedirectedLocation; + } + + public boolean isIdPRedirection() { + return (mRedirectedLocation != null && + (mRedirectedLocation.toUpperCase().contains("SAML") || + mRedirectedLocation.toLowerCase().contains("wayf"))); + } + +} diff --git a/src/com/owncloud/android/operations/RemoveFileOperation.java b/src/com/owncloud/android/operations/RemoveFileOperation.java new file mode 100644 index 00000000..f8a6c4ab --- /dev/null +++ b/src/com/owncloud/android/operations/RemoveFileOperation.java @@ -0,0 +1,106 @@ +/* ownCloud Android client application + * 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.operations; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.jackrabbit.webdav.client.methods.DeleteMethod; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.OCFile; + + +import eu.alefzero.webdav.WebdavClient; +import eu.alefzero.webdav.WebdavUtils; + +/** + * Remote operation performing the removal of a remote file or folder in the ownCloud server. + * + * @author David A. Velasco + */ +public class RemoveFileOperation extends RemoteOperation { + + private static final String TAG = RemoveFileOperation.class.getSimpleName(); + + private static final int REMOVE_READ_TIMEOUT = 10000; + private static final int REMOVE_CONNECTION_TIMEOUT = 5000; + + OCFile mFileToRemove; + boolean mDeleteLocalCopy; + DataStorageManager mDataStorageManager; + + + /** + * Constructor + * + * @param fileToRemove OCFile instance describing the remote file or folder to remove from the server + * @param deleteLocalCopy When 'true', and a local copy of the file exists, it is also removed. + * @param storageManager Reference to the local database corresponding to the account where the file is contained. + */ + public RemoveFileOperation(OCFile fileToRemove, boolean deleteLocalCopy, DataStorageManager storageManager) { + mFileToRemove = fileToRemove; + mDeleteLocalCopy = deleteLocalCopy; + mDataStorageManager = storageManager; + } + + + /** + * Getter for the file to remove (or removed, if the operation was successfully performed). + * + * @return File to remove or already removed. + */ + public OCFile getFile() { + return mFileToRemove; + } + + + /** + * Performs the remove operation + * + * @param client Client object to communicate with the remote ownCloud server. + */ + @Override + protected RemoteOperationResult run(WebdavClient client) { + RemoteOperationResult result = null; + DeleteMethod delete = null; + try { + delete = new DeleteMethod(client.getBaseUri() + WebdavUtils.encodePath(mFileToRemove.getRemotePath())); + int status = client.executeMethod(delete, REMOVE_READ_TIMEOUT, REMOVE_CONNECTION_TIMEOUT); + if (delete.succeeded() || status == HttpStatus.SC_NOT_FOUND) { + if (mFileToRemove.isDirectory()) { + mDataStorageManager.removeDirectory(mFileToRemove, true, mDeleteLocalCopy); + } else { + mDataStorageManager.removeFile(mFileToRemove, mDeleteLocalCopy); + } + } + delete.getResponseBodyAsString(); // exhaust the response, although not interesting + result = new RemoteOperationResult((delete.succeeded() || status == HttpStatus.SC_NOT_FOUND), status, delete.getResponseHeaders()); + Log_OC.i(TAG, "Remove " + mFileToRemove.getRemotePath() + ": " + result.getLogMessage()); + + } catch (Exception e) { + result = new RemoteOperationResult(e); + Log_OC.e(TAG, "Remove " + mFileToRemove.getRemotePath() + ": " + result.getLogMessage(), e); + + } finally { + if (delete != null) + delete.releaseConnection(); + } + return result; + } + +} diff --git a/src/com/owncloud/android/operations/RenameFileOperation.java b/src/com/owncloud/android/operations/RenameFileOperation.java new file mode 100644 index 00000000..86d27122 --- /dev/null +++ b/src/com/owncloud/android/operations/RenameFileOperation.java @@ -0,0 +1,248 @@ +/* ownCloud Android client application + * 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.operations; + +import java.io.File; +import java.io.IOException; + +import org.apache.jackrabbit.webdav.client.methods.DavMethodBase; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.utils.FileStorageUtils; +//import org.apache.jackrabbit.webdav.client.methods.MoveMethod; + +import android.accounts.Account; + + +import eu.alefzero.webdav.WebdavClient; +import eu.alefzero.webdav.WebdavUtils; + +/** + * Remote operation performing the rename of a remote file (or folder?) in the ownCloud server. + * + * @author David A. Velasco + */ +public class RenameFileOperation extends RemoteOperation { + + private static final String TAG = RenameFileOperation.class.getSimpleName(); + + private static final int RENAME_READ_TIMEOUT = 10000; + private static final int RENAME_CONNECTION_TIMEOUT = 5000; + + + private OCFile mFile; + private Account mAccount; + private String mNewName; + private String mNewRemotePath; + private DataStorageManager mStorageManager; + + + /** + * Constructor + * + * @param file OCFile instance describing the remote file or folder to rename + * @param account OwnCloud account containing the remote file + * @param newName New name to set as the name of file. + * @param storageManager Reference to the local database corresponding to the account where the file is contained. + */ + public RenameFileOperation(OCFile file, Account account, String newName, DataStorageManager storageManager) { + mFile = file; + mAccount = account; + mNewName = newName; + mNewRemotePath = null; + mStorageManager = storageManager; + } + + public OCFile getFile() { + return mFile; + } + + + /** + * Performs the rename operation. + * + * @param client Client object to communicate with the remote ownCloud server. + */ + @Override + protected RemoteOperationResult run(WebdavClient client) { + RemoteOperationResult result = null; + + LocalMoveMethod move = null; + mNewRemotePath = null; + try { + if (mNewName.equals(mFile.getFileName())) { + return new RemoteOperationResult(ResultCode.OK); + } + + String parent = (new File(mFile.getRemotePath())).getParent(); + parent = (parent.endsWith(OCFile.PATH_SEPARATOR)) ? parent : parent + OCFile.PATH_SEPARATOR; + mNewRemotePath = parent + mNewName; + if (mFile.isDirectory()) { + mNewRemotePath += OCFile.PATH_SEPARATOR; + } + + // check if the new name is valid in the local file system + if (!isValidNewName()) { + return new RemoteOperationResult(ResultCode.INVALID_LOCAL_FILE_NAME); + } + + // check if a file with the new name already exists + if (client.existsFile(mNewRemotePath) || // remote check could fail by network failure. by indeterminate behavior of HEAD for folders ... + mStorageManager.getFileByPath(mNewRemotePath) != null) { // ... so local check is convenient + return new RemoteOperationResult(ResultCode.INVALID_OVERWRITE); + } + move = new LocalMoveMethod( client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath()), + client.getBaseUri() + WebdavUtils.encodePath(mNewRemotePath)); + int status = client.executeMethod(move, RENAME_READ_TIMEOUT, RENAME_CONNECTION_TIMEOUT); + if (move.succeeded()) { + + if (mFile.isDirectory()) { + saveLocalDirectory(); + + } else { + saveLocalFile(); + + } + + /* + *} else if (mFile.isDirectory() && (status == 207 || status >= 500)) { + * // TODO + * // if server fails in the rename of a folder, some children files could have been moved to a folder with the new name while some others + * // stayed in the old folder; + * // + * // easiest and heaviest solution is synchronizing the parent folder (or the full account); + * // + * // a better solution is synchronizing the folders with the old and new names; + *} + */ + + } + + move.getResponseBodyAsString(); // exhaust response, although not interesting + result = new RemoteOperationResult(move.succeeded(), status, move.getResponseHeaders()); + Log_OC.i(TAG, "Rename " + mFile.getRemotePath() + " to " + mNewRemotePath + ": " + result.getLogMessage()); + + } catch (Exception e) { + result = new RemoteOperationResult(e); + Log_OC.e(TAG, "Rename " + mFile.getRemotePath() + " to " + ((mNewRemotePath==null) ? mNewName : mNewRemotePath) + ": " + result.getLogMessage(), e); + + } finally { + if (move != null) + move.releaseConnection(); + } + return result; + } + + + private void saveLocalDirectory() { + mStorageManager.moveDirectory(mFile, mNewRemotePath); + String localPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); + File localDir = new File(localPath); + if (localDir.exists()) { + localDir.renameTo(new File(FileStorageUtils.getSavePath(mAccount.name) + mNewRemotePath)); + // TODO - if renameTo fails, children files that are already down will result unlinked + } + } + + private void saveLocalFile() { + mFile.setFileName(mNewName); + + // try to rename the local copy of the file + if (mFile.isDown()) { + File f = new File(mFile.getStoragePath()); + String parentStoragePath = f.getParent(); + if (!parentStoragePath.endsWith(File.separator)) + parentStoragePath += File.separator; + if (f.renameTo(new File(parentStoragePath + mNewName))) { + mFile.setStoragePath(parentStoragePath + mNewName); + } + // else - NOTHING: the link to the local file is kept although the local name can't be updated + // TODO - study conditions when this could be a problem + } + + mStorageManager.saveFile(mFile); + } + + /** + * Checks if the new name to set is valid in the file system + * + * The only way to be sure is trying to create a file with that name. It's made in the temporal directory + * for downloads, out of any account, and then removed. + * + * IMPORTANT: The test must be made in the same file system where files are download. The internal storage + * could be formatted with a different file system. + * + * TODO move this method, and maybe FileDownload.get***Path(), to a class with utilities specific for the interactions with the file system + * + * @return 'True' if a temporal file named with the name to set could be created in the file system where + * local files are stored. + * @throws IOException When the temporal folder can not be created. + */ + private boolean isValidNewName() throws IOException { + // check tricky names + if (mNewName == null || mNewName.length() <= 0 || mNewName.contains(File.separator) || mNewName.contains("%")) { + return false; + } + // create a test file + String tmpFolderName = FileStorageUtils.getTemporalPath(""); + File testFile = new File(tmpFolderName + mNewName); + File tmpFolder = testFile.getParentFile(); + tmpFolder.mkdirs(); + if (!tmpFolder.isDirectory()) { + throw new IOException("Unexpected error: temporal directory could not be created"); + } + try { + testFile.createNewFile(); // return value is ignored; it could be 'false' because the file already existed, that doesn't invalidate the name + } catch (IOException e) { + Log_OC.i(TAG, "Test for validity of name " + mNewName + " in the file system failed"); + return false; + } + boolean result = (testFile.exists() && testFile.isFile()); + + // cleaning ; result is ignored, since there is not much we could do in case of failure, but repeat and repeat... + testFile.delete(); + + return result; + } + + + // move operation + private class LocalMoveMethod extends DavMethodBase { + + public LocalMoveMethod(String uri, String dest) { + super(uri); + addRequestHeader(new org.apache.commons.httpclient.Header("Destination", dest)); + } + + @Override + public String getName() { + return "MOVE"; + } + + @Override + protected boolean isSuccess(int status) { + return status == 201 || status == 204; + } + + } + + +} diff --git a/src/com/owncloud/android/operations/SynchronizeFileOperation.java b/src/com/owncloud/android/operations/SynchronizeFileOperation.java new file mode 100644 index 00000000..211ba705 --- /dev/null +++ b/src/com/owncloud/android/operations/SynchronizeFileOperation.java @@ -0,0 +1,235 @@ +/* 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.operations; + +import org.apache.http.HttpStatus; +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.MultiStatus; +import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.services.FileDownloader; +import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; + +import android.accounts.Account; +import android.content.Context; +import android.content.Intent; + + +import eu.alefzero.webdav.WebdavClient; +import eu.alefzero.webdav.WebdavEntry; +import eu.alefzero.webdav.WebdavUtils; + +public class SynchronizeFileOperation extends RemoteOperation { + + private String TAG = SynchronizeFileOperation.class.getSimpleName(); + private static final int SYNC_READ_TIMEOUT = 10000; + private static final int SYNC_CONNECTION_TIMEOUT = 5000; + + private OCFile mLocalFile; + private OCFile mServerFile; + private DataStorageManager mStorageManager; + private Account mAccount; + private boolean mSyncFileContents; + private boolean mLocalChangeAlreadyKnown; + private Context mContext; + + private boolean mTransferWasRequested = false; + + public SynchronizeFileOperation( + OCFile localFile, + OCFile serverFile, // make this null to let the operation checks the server; added to reuse info from SynchronizeFolderOperation + DataStorageManager storageManager, + Account account, + boolean syncFileContents, + boolean localChangeAlreadyKnown, + Context context) { + + mLocalFile = localFile; + mServerFile = serverFile; + mStorageManager = storageManager; + mAccount = account; + mSyncFileContents = syncFileContents; + mLocalChangeAlreadyKnown = localChangeAlreadyKnown; + mContext = context; + } + + + @Override + protected RemoteOperationResult run(WebdavClient client) { + + PropFindMethod propfind = null; + RemoteOperationResult result = null; + mTransferWasRequested = false; + try { + if (!mLocalFile.isDown()) { + /// easy decision + requestForDownload(mLocalFile); + result = new RemoteOperationResult(ResultCode.OK); + + } else { + /// local copy in the device -> need to think a bit more before do anything + + if (mServerFile == null) { + /// take the duty of check the server for the current state of the file there + propfind = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mLocalFile.getRemotePath()), + DavConstants.PROPFIND_ALL_PROP, + DavConstants.DEPTH_0); + int status = client.executeMethod(propfind, SYNC_READ_TIMEOUT, SYNC_CONNECTION_TIMEOUT); + boolean isMultiStatus = status == HttpStatus.SC_MULTI_STATUS; + if (isMultiStatus) { + MultiStatus resp = propfind.getResponseBodyAsMultiStatus(); + WebdavEntry we = new WebdavEntry(resp.getResponses()[0], + client.getBaseUri().getPath()); + mServerFile = fillOCFile(we); + mServerFile.setLastSyncDateForProperties(System.currentTimeMillis()); + + } else { + client.exhaustResponse(propfind.getResponseBodyAsStream()); + result = new RemoteOperationResult(false, status, propfind.getResponseHeaders()); + } + } + + if (result == null) { // true if the server was not checked. nothing was wrong with the remote request + + /// check changes in server and local file + boolean serverChanged = false; + if (mServerFile.getEtag() != null) { + serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag())); // TODO could this be dangerous when the user upgrades the server from non-tagged to tagged? + } else { + // server without etags + serverChanged = (mServerFile.getModificationTimestamp() > mLocalFile.getModificationTimestampAtLastSyncForData()); + } + boolean localChanged = (mLocalChangeAlreadyKnown || mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData()); + // TODO this will be always true after the app is upgraded to database version 2; will result in unnecessary uploads + + /// decide action to perform depending upon changes + if (localChanged && serverChanged) { + result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); + + } else if (localChanged) { + if (mSyncFileContents) { + requestForUpload(mLocalFile); + // the local update of file properties will be done by the FileUploader service when the upload finishes + } else { + // NOTHING TO DO HERE: updating the properties of the file in the server without uploading the contents would be stupid; + // So, an instance of SynchronizeFileOperation created with syncFileContents == false is completely useless when we suspect + // that an upload is necessary (for instance, in FileObserverService). + } + result = new RemoteOperationResult(ResultCode.OK); + + } else if (serverChanged) { + if (mSyncFileContents) { + requestForDownload(mLocalFile); // local, not server; we won't to keep the value of keepInSync! + // the update of local data will be done later by the FileUploader service when the upload finishes + } else { + // TODO CHECK: is this really useful in some point in the code? + mServerFile.setKeepInSync(mLocalFile.keepInSync()); + mServerFile.setLastSyncDateForData(mLocalFile.getLastSyncDateForData()); + mServerFile.setStoragePath(mLocalFile.getStoragePath()); + mServerFile.setParentId(mLocalFile.getParentId()); + mStorageManager.saveFile(mServerFile); + + } + result = new RemoteOperationResult(ResultCode.OK); + + } else { + // nothing changed, nothing to do + result = new RemoteOperationResult(ResultCode.OK); + } + + } + + } + + Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " + result.getLogMessage()); + + } catch (Exception e) { + result = new RemoteOperationResult(e); + Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", file " + (mLocalFile != null ? mLocalFile.getRemotePath() : "NULL") + ": " + result.getLogMessage(), result.getException()); + + } finally { + if (propfind != null) + propfind.releaseConnection(); + } + return result; + } + + + /** + * Requests for an upload to the FileUploader service + * + * @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); + /*i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath); // 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); + mContext.startService(i); + mTransferWasRequested = true; + } + + + /** + * Requests for a download to the FileDownloader service + * + * @param file OCFile object representing the file to download + */ + private void requestForDownload(OCFile file) { + Intent i = new Intent(mContext, FileDownloader.class); + i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount); + i.putExtra(FileDownloader.EXTRA_FILE, file); + mContext.startService(i); + mTransferWasRequested = true; + } + + + /** + * Creates and populates a new {@link OCFile} object with the data read from the server. + * + * @param we WebDAV entry read from the server for a WebDAV resource (remote file or folder). + * @return New OCFile instance representing the remote resource described by we. + */ + private OCFile fillOCFile(WebdavEntry we) { + OCFile file = new OCFile(we.decodedPath()); + file.setCreationTimestamp(we.createTimestamp()); + file.setFileLength(we.contentLength()); + file.setMimetype(we.contentType()); + file.setModificationTimestamp(we.modifiedTimestamp()); + return file; + } + + + public boolean transferWasRequested() { + return mTransferWasRequested; + } + + + public OCFile getLocalFile() { + return mLocalFile; + } + +} diff --git a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java new file mode 100644 index 00000000..3d09b47e --- /dev/null +++ b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java @@ -0,0 +1,365 @@ +/* ownCloud Android client application + * 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.operations; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import org.apache.http.HttpStatus; +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.MultiStatus; +import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.utils.FileStorageUtils; + +import android.accounts.Account; +import android.content.Context; + + +import eu.alefzero.webdav.WebdavClient; +import eu.alefzero.webdav.WebdavEntry; +import eu.alefzero.webdav.WebdavUtils; + + +/** + * Remote operation performing the synchronization a the contents of a remote folder with the local database + * + * @author David A. Velasco + */ +public class SynchronizeFolderOperation extends RemoteOperation { + + private static final String TAG = SynchronizeFolderOperation.class.getSimpleName(); + + /** Remote folder to synchronize */ + private String mRemotePath; + + /** Timestamp for the synchronization in progress */ + private long mCurrentSyncTime; + + /** Id of the folder to synchronize in the local database */ + private long mParentId; + + /** Access to the local database */ + private DataStorageManager mStorageManager; + + /** Account where the file to synchronize belongs */ + private Account mAccount; + + /** Android context; necessary to send requests to the download service; maybe something to refactor */ + private Context mContext; + + /** Files and folders contained in the synchronized folder */ + private List mChildren; + + private int mConflictsFound; + + private int mFailsInFavouritesFound; + + private Map mForgottenLocalFiles; + + + public SynchronizeFolderOperation( String remotePath, + long currentSyncTime, + long parentId, + DataStorageManager dataStorageManager, + Account account, + Context context ) { + mRemotePath = remotePath; + mCurrentSyncTime = currentSyncTime; + mParentId = parentId; + mStorageManager = dataStorageManager; + mAccount = account; + mContext = context; + mForgottenLocalFiles = new HashMap(); + } + + + public int getConflictsFound() { + return mConflictsFound; + } + + public int getFailsInFavouritesFound() { + return mFailsInFavouritesFound; + } + + public Map getForgottenLocalFiles() { + return mForgottenLocalFiles; + } + + /** + * Returns the list of files and folders contained in the synchronized folder, if called after synchronization is complete. + * + * @return List of files and folders contained in the synchronized folder. + */ + public List getChildren() { + return mChildren; + } + + + @Override + protected RemoteOperationResult run(WebdavClient client) { + RemoteOperationResult result = null; + mFailsInFavouritesFound = 0; + mConflictsFound = 0; + mForgottenLocalFiles.clear(); + + // code before in FileSyncAdapter.fetchData + PropFindMethod query = null; + try { + Log_OC.d(TAG, "Synchronizing " + mAccount.name + ", fetching files in " + mRemotePath); + + // remote request + query = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath), + DavConstants.PROPFIND_ALL_PROP, + DavConstants.DEPTH_1); + int status = client.executeMethod(query); + + // check and process response - /// TODO take into account all the possible status per child-resource + if (isMultiStatus(status)) { + MultiStatus resp = query.getResponseBodyAsMultiStatus(); + + // synchronize properties of the parent folder, if necessary + if (mParentId == DataStorageManager.ROOT_PARENT_ID) { + WebdavEntry we = new WebdavEntry(resp.getResponses()[0], client.getBaseUri().getPath()); + OCFile parent = fillOCFile(we); + mStorageManager.saveFile(parent); + mParentId = parent.getFileId(); + } + + // read contents in folder + List updatedFiles = new Vector(resp.getResponses().length - 1); + List filesToSyncContents = new Vector(); + for (int i = 1; i < resp.getResponses().length; ++i) { + /// new OCFile instance with the data from the server + WebdavEntry we = new WebdavEntry(resp.getResponses()[i], client.getBaseUri().getPath()); + OCFile file = fillOCFile(we); + + /// set data about local state, keeping unchanged former data if existing + file.setLastSyncDateForProperties(mCurrentSyncTime); + OCFile oldFile = mStorageManager.getFileByPath(file.getRemotePath()); + if (oldFile != null) { + file.setKeepInSync(oldFile.keepInSync()); + file.setLastSyncDateForData(oldFile.getLastSyncDateForData()); + file.setModificationTimestampAtLastSyncForData(oldFile.getModificationTimestampAtLastSyncForData()); // must be kept unchanged when the file contents are not updated + checkAndFixForeignStoragePath(oldFile); + file.setStoragePath(oldFile.getStoragePath()); + } + + /// scan default location if local copy of file is not linked in OCFile instance + if (file.getStoragePath() == null && !file.isDirectory()) { + File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); + if (f.exists()) { + file.setStoragePath(f.getAbsolutePath()); + file.setLastSyncDateForData(f.lastModified()); + } + } + + /// prepare content synchronization for kept-in-sync files + if (file.keepInSync()) { + SynchronizeFileOperation operation = new SynchronizeFileOperation( oldFile, + file, + mStorageManager, + mAccount, + true, + false, + mContext + ); + filesToSyncContents.add(operation); + } + + updatedFiles.add(file); + } + + // save updated contents in local database; all at once, trying to get a best performance in database update (not a big deal, indeed) + mStorageManager.saveFiles(updatedFiles); + + // request for the synchronization of files AFTER saving last properties + SynchronizeFileOperation op = null; + RemoteOperationResult contentsResult = null; + for (int i=0; i < filesToSyncContents.size(); i++) { + op = filesToSyncContents.get(i); + contentsResult = op.execute(client); // returns without waiting for upload or download finishes + if (!contentsResult.isSuccess()) { + if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) { + mConflictsFound++; + } else { + mFailsInFavouritesFound++; + if (contentsResult.getException() != null) { + Log_OC.e(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage(), contentsResult.getException()); + } else { + Log_OC.e(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage()); + } + } + } // won't let these fails break the synchronization process + } + + + // removal of obsolete files + mChildren = mStorageManager.getDirectoryContent(mStorageManager.getFileById(mParentId)); + OCFile file; + String currentSavePath = FileStorageUtils.getSavePath(mAccount.name); + for (int i=0; i < mChildren.size(); ) { + file = mChildren.get(i); + if (file.getLastSyncDateForProperties() != mCurrentSyncTime) { + Log_OC.d(TAG, "removing file: " + file); + mStorageManager.removeFile(file, (file.isDown() && file.getStoragePath().startsWith(currentSavePath))); + mChildren.remove(i); + } else { + i++; + } + } + + } else { + client.exhaustResponse(query.getResponseBodyAsStream()); + } + + // prepare result object + if (isMultiStatus(status)) { + if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) { + result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); // should be different result, but will do the job + + } else { + result = new RemoteOperationResult(true, status, query.getResponseHeaders()); + } + } else { + result = new RemoteOperationResult(false, status, query.getResponseHeaders()); + } + + + + } catch (Exception e) { + result = new RemoteOperationResult(e); + + + } finally { + if (query != null) + query.releaseConnection(); // let the connection available for other methods + if (result.isSuccess()) { + Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage()); + } else { + if (result.isException()) { + Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage(), result.getException()); + } else { + Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage()); + } + } + } + + return result; + } + + + public boolean isMultiStatus(int status) { + return (status == HttpStatus.SC_MULTI_STATUS); + } + + + /** + * Creates and populates a new {@link OCFile} object with the data read from the server. + * + * @param we WebDAV entry read from the server for a WebDAV resource (remote file or folder). + * @return New OCFile instance representing the remote resource described by we. + */ + private OCFile fillOCFile(WebdavEntry we) { + OCFile file = new OCFile(we.decodedPath()); + file.setCreationTimestamp(we.createTimestamp()); + file.setFileLength(we.contentLength()); + file.setMimetype(we.contentType()); + file.setModificationTimestamp(we.modifiedTimestamp()); + file.setParentId(mParentId); + return file; + } + + + /** + * Checks the storage path of the OCFile received as parameter. If it's out of the local ownCloud folder, + * tries to copy the file inside it. + * + * If the copy fails, the link to the local file is nullified. The account of forgotten files is kept in + * {@link #mForgottenLocalFiles} + * + * @param file File to check and fix. + */ + private void checkAndFixForeignStoragePath(OCFile file) { + String storagePath = file.getStoragePath(); + String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, file); + if (storagePath != null && !storagePath.equals(expectedPath)) { + /// fix storagePaths out of the local ownCloud folder + File originalFile = new File(storagePath); + if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) { + mForgottenLocalFiles.put(file.getRemotePath(), storagePath); + file.setStoragePath(null); + + } else { + InputStream in = null; + OutputStream out = null; + try { + File expectedFile = new File(expectedPath); + File expectedParent = expectedFile.getParentFile(); + expectedParent.mkdirs(); + if (!expectedParent.isDirectory()) { + throw new IOException("Unexpected error: parent directory could not be created"); + } + expectedFile.createNewFile(); + if (!expectedFile.isFile()) { + throw new IOException("Unexpected error: target file could not be created"); + } + in = new FileInputStream(originalFile); + out = new FileOutputStream(expectedFile); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0){ + out.write(buf, 0, len); + } + file.setStoragePath(expectedPath); + + } catch (Exception e) { + Log_OC.e(TAG, "Exception while copying foreign file " + expectedPath, e); + mForgottenLocalFiles.put(file.getRemotePath(), storagePath); + file.setStoragePath(null); + + } finally { + try { + if (in != null) in.close(); + } catch (Exception e) { + Log_OC.d(TAG, "Weird exception while closing input stream for " + storagePath + " (ignoring)", e); + } + try { + if (out != null) out.close(); + } catch (Exception e) { + Log_OC.d(TAG, "Weird exception while closing output stream for " + expectedPath + " (ignoring)", e); + } + } + } + } + } + + +} diff --git a/src/com/owncloud/android/operations/UpdateOCVersionOperation.java b/src/com/owncloud/android/operations/UpdateOCVersionOperation.java new file mode 100644 index 00000000..2e116ac6 --- /dev/null +++ b/src/com/owncloud/android/operations/UpdateOCVersionOperation.java @@ -0,0 +1,109 @@ +/* ownCloud Android client application + * 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.operations; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.GetMethod; +import org.json.JSONException; +import org.json.JSONObject; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.authentication.AccountAuthenticator; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.utils.OwnCloudVersion; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.content.Context; + + +import eu.alefzero.webdav.WebdavClient; + +/** + * Remote operation that checks the version of an ownCloud server and stores it locally + * + * @author David A. Velasco + */ +public class UpdateOCVersionOperation extends RemoteOperation { + + private static final String TAG = UpdateOCVersionOperation.class.getSimpleName(); + + private Account mAccount; + private Context mContext; + + + public UpdateOCVersionOperation(Account account, Context context) { + mAccount = account; + mContext = context; + } + + + @Override + protected RemoteOperationResult run(WebdavClient client) { + AccountManager accountMngr = AccountManager.get(mContext); + String statUrl = accountMngr.getUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL); + statUrl += AccountUtils.STATUS_PATH; + RemoteOperationResult result = null; + GetMethod get = null; + try { + get = new GetMethod(statUrl); + int status = client.executeMethod(get); + if (status != HttpStatus.SC_OK) { + client.exhaustResponse(get.getResponseBodyAsStream()); + result = new RemoteOperationResult(false, status, get.getResponseHeaders()); + + } else { + String response = get.getResponseBodyAsString(); + if (response != null) { + JSONObject json = new JSONObject(response); + if (json != null && json.getString("version") != null) { + OwnCloudVersion ocver = new OwnCloudVersion(json.getString("version")); + if (ocver.isVersionValid()) { + accountMngr.setUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION, ocver.toString()); + Log_OC.d(TAG, "Got new OC version " + ocver.toString()); + result = new RemoteOperationResult(ResultCode.OK); + + } else { + Log_OC.w(TAG, "Invalid version number received from server: " + json.getString("version")); + result = new RemoteOperationResult(RemoteOperationResult.ResultCode.BAD_OC_VERSION); + } + } + } + if (result == null) { + result = new RemoteOperationResult(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED); + } + } + Log_OC.i(TAG, "Check for update of ownCloud server version at " + client.getBaseUri() + ": " + result.getLogMessage()); + + } catch (JSONException e) { + result = new RemoteOperationResult(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED); + Log_OC.e(TAG, "Check for update of ownCloud server version at " + client.getBaseUri() + ": " + result.getLogMessage(), e); + + } catch (Exception e) { + result = new RemoteOperationResult(e); + Log_OC.e(TAG, "Check for update of ownCloud server version at " + client.getBaseUri() + ": " + result.getLogMessage(), e); + + } finally { + if (get != null) + get.releaseConnection(); + } + return result; + } + +} diff --git a/src/com/owncloud/android/operations/UploadFileOperation.java b/src/com/owncloud/android/operations/UploadFileOperation.java new file mode 100644 index 00000000..936ac01e --- /dev/null +++ b/src/com/owncloud/android/operations/UploadFileOperation.java @@ -0,0 +1,429 @@ +/* ownCloud Android client application + * 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.operations; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.commons.httpclient.methods.RequestEntity; +import org.apache.http.HttpStatus; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.network.ProgressiveDataTransferer; +import com.owncloud.android.operations.RemoteOperation; +import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.utils.FileStorageUtils; + +import android.accounts.Account; + + +import eu.alefzero.webdav.FileRequestEntity; +import eu.alefzero.webdav.OnDatatransferProgressListener; +import eu.alefzero.webdav.WebdavClient; +import eu.alefzero.webdav.WebdavUtils; + +/** + * Remote operation performing the upload of a file to an ownCloud server + * + * @author David A. Velasco + */ +public class UploadFileOperation extends RemoteOperation { + + private static final String TAG = UploadFileOperation.class.getSimpleName(); + + private Account mAccount; + private OCFile mFile; + private OCFile mOldFile; + private String mRemotePath = null; + private boolean mIsInstant = false; + private boolean mRemoteFolderToBeCreated = false; + private boolean mForceOverwrite = false; + private int mLocalBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY; + private boolean mWasRenamed = false; + private String mOriginalFileName = null; + private String mOriginalStoragePath = null; + PutMethod mPutMethod = null; + private Set mDataTransferListeners = new HashSet(); + private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false); + + protected RequestEntity mEntity = null; + + + public UploadFileOperation( Account account, + OCFile file, + boolean isInstant, + boolean forceOverwrite, + int localBehaviour) { + if (account == null) + throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation creation"); + if (file == null) + throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation"); + if (file.getStoragePath() == null || file.getStoragePath().length() <= 0 + || !(new File(file.getStoragePath()).exists())) { + throw new IllegalArgumentException( + "Illegal file in UploadFileOperation; storage path invalid or file not found: " + + file.getStoragePath()); + } + + mAccount = account; + mFile = file; + mRemotePath = file.getRemotePath(); + mIsInstant = isInstant; + mForceOverwrite = forceOverwrite; + mLocalBehaviour = localBehaviour; + mOriginalStoragePath = mFile.getStoragePath(); + mOriginalFileName = mFile.getFileName(); + } + + public Account getAccount() { + return mAccount; + } + + public String getFileName() { + return mOriginalFileName; + } + + public OCFile getFile() { + return mFile; + } + + public OCFile getOldFile() { + return mOldFile; + } + + public String getOriginalStoragePath() { + return mOriginalStoragePath; + } + + public String getStoragePath() { + return mFile.getStoragePath(); + } + + public String getRemotePath() { + return mFile.getRemotePath(); + } + + public String getMimeType() { + return mFile.getMimetype(); + } + + public boolean isInstant() { + return mIsInstant; + } + + public boolean isRemoteFolderToBeCreated() { + return mRemoteFolderToBeCreated; + } + + public void setRemoteFolderToBeCreated() { + mRemoteFolderToBeCreated = true; + } + + public boolean getForceOverwrite() { + return mForceOverwrite; + } + + public boolean wasRenamed() { + return mWasRenamed; + } + + public Set getDataTransferListeners() { + return mDataTransferListeners; + } + + public void addDatatransferProgressListener (OnDatatransferProgressListener listener) { + synchronized (mDataTransferListeners) { + mDataTransferListeners.add(listener); + } + if (mEntity != null) { + ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListener(listener); + } + } + + public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) { + synchronized (mDataTransferListeners) { + mDataTransferListeners.remove(listener); + } + if (mEntity != null) { + ((ProgressiveDataTransferer)mEntity).removeDatatransferProgressListener(listener); + } + } + + @Override + protected RemoteOperationResult run(WebdavClient client) { + RemoteOperationResult result = null; + boolean localCopyPassed = false, nameCheckPassed = false; + File temporalFile = null, originalFile = new File(mOriginalStoragePath), expectedFile = null; + try { + // / rename the file to upload, if necessary + if (!mForceOverwrite) { + String remotePath = getAvailableRemotePath(client, mRemotePath); + mWasRenamed = !remotePath.equals(mRemotePath); + if (mWasRenamed) { + createNewOCFile(remotePath); + } + } + nameCheckPassed = true; + + String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); // / + // not + // before + // getAvailableRemotePath() + // !!! + expectedFile = new File(expectedPath); + + // / 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) { + + if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) { + result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL); + return result; // error condition when the file should be + // copied + + } else { + String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath(); + mFile.setStoragePath(temporalPath); + temporalFile = new File(temporalPath); + if (!mOriginalStoragePath.equals(temporalPath)) { // preventing + // weird + // but + // possible + // situation + InputStream in = null; + OutputStream out = null; + try { + File temporalParent = temporalFile.getParentFile(); + temporalParent.mkdirs(); + if (!temporalParent.isDirectory()) { + 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"); + } + in = new FileInputStream(originalFile); + out = new FileOutputStream(temporalFile); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + + } catch (Exception e) { + result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED); + return result; + + } finally { + try { + if (in != null) + in.close(); + } catch (Exception e) { + Log_OC.d(TAG, "Weird exception while closing input stream for " + mOriginalStoragePath + " (ignoring)", e); + } + try { + if (out != null) + out.close(); + } catch (Exception e) { + Log_OC.d(TAG, "Weird exception while closing output stream for " + expectedPath + " (ignoring)", e); + } + } + } + } + } + localCopyPassed = true; + + // / perform the upload + synchronized (mCancellationRequested) { + if (mCancellationRequested.get()) { + throw new OperationCancelledException(); + } else { + mPutMethod = new PutMethod(client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath())); + } + } + int status = uploadFile(client); + + // / move local temporal file or original file to its corresponding + // location in the ownCloud local folder + if (isSuccess(status)) { + if (mLocalBehaviour == FileUploader.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; + } + } + } + } + + result = new RemoteOperationResult(isSuccess(status), status, (mPutMethod != null ? mPutMethod.getResponseHeaders() : null)); + + } catch (Exception e) { + // TODO something cleaner with cancellations + if (mCancellationRequested.get()) { + result = new RemoteOperationResult(new OperationCancelledException()); + } else { + result = new RemoteOperationResult(e); + } + + } finally { + if (temporalFile != null && !originalFile.equals(temporalFile)) { + temporalFile.delete(); + } + if (result.isSuccess()) { + Log_OC.i(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage()); + } else { + if (result.getException() != null) { + String complement = ""; + if (!nameCheckPassed) { + complement = " (while checking file existence in server)"; + } else if (!localCopyPassed) { + complement = " (while copying local file to " + FileStorageUtils.getSavePath(mAccount.name) + + ")"; + } + 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()); + } + } + } + + return result; + } + + private void createNewOCFile(String newRemotePath) { + // a new OCFile instance must be created for a new remote path + OCFile newFile = new OCFile(newRemotePath); + newFile.setCreationTimestamp(mFile.getCreationTimestamp()); + newFile.setFileLength(mFile.getFileLength()); + newFile.setMimetype(mFile.getMimetype()); + newFile.setModificationTimestamp(mFile.getModificationTimestamp()); + newFile.setModificationTimestampAtLastSyncForData(mFile.getModificationTimestampAtLastSyncForData()); + // newFile.setEtag(mFile.getEtag()) + newFile.setKeepInSync(mFile.keepInSync()); + newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties()); + newFile.setLastSyncDateForData(mFile.getLastSyncDateForData()); + newFile.setStoragePath(mFile.getStoragePath()); + newFile.setParentId(mFile.getParentId()); + mOldFile = mFile; + mFile = newFile; + } + + public boolean isSuccess(int status) { + return ((status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED || status == HttpStatus.SC_NO_CONTENT)); + } + + protected int uploadFile(WebdavClient client) throws HttpException, IOException, OperationCancelledException { + int status = -1; + try { + File f = new File(mFile.getStoragePath()); + mEntity = new FileRequestEntity(f, getMimeType()); + synchronized (mDataTransferListeners) { + ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListeners(mDataTransferListeners); + } + mPutMethod.setRequestEntity(mEntity); + status = client.executeMethod(mPutMethod); + client.exhaustResponse(mPutMethod.getResponseBodyAsStream()); + + } finally { + mPutMethod.releaseConnection(); // let the connection available for + // other methods + } + return status; + } + + /** + * Checks if remotePath does not exist in the server and returns it, or adds + * a suffix to it in order to avoid the server file is overwritten. + * + * @param string + * @return + */ + private String getAvailableRemotePath(WebdavClient wc, String remotePath) throws Exception { + boolean check = wc.existsFile(remotePath); + if (!check) { + return remotePath; + } + + int pos = remotePath.lastIndexOf("."); + String suffix = ""; + String extension = ""; + if (pos >= 0) { + extension = remotePath.substring(pos + 1); + remotePath = remotePath.substring(0, pos); + } + int count = 2; + do { + suffix = " (" + count + ")"; + if (pos >= 0) + check = wc.existsFile(remotePath + suffix + "." + extension); + else + check = wc.existsFile(remotePath + suffix); + count++; + } while (check); + + if (pos >= 0) { + return remotePath + suffix + "." + extension; + } else { + return remotePath + suffix; + } + } + + public void cancel() { + synchronized (mCancellationRequested) { + mCancellationRequested.set(true); + if (mPutMethod != null) + mPutMethod.abort(); + } + } + +} diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java new file mode 100644 index 00000000..7f6a1a4f --- /dev/null +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -0,0 +1,302 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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.providers; + +import java.util.HashMap; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.db.ProviderMeta; +import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; + + + +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.text.TextUtils; + +/** + * The ContentProvider for the ownCloud App. + * + * @author Bartek Przybylski + * + */ +public class FileContentProvider extends ContentProvider { + + private DataBaseHelper mDbHelper; + + private static HashMap mProjectionMap; + static { + mProjectionMap = new HashMap(); + mProjectionMap.put(ProviderTableMeta._ID, ProviderTableMeta._ID); + mProjectionMap.put(ProviderTableMeta.FILE_PARENT, + ProviderTableMeta.FILE_PARENT); + mProjectionMap.put(ProviderTableMeta.FILE_PATH, + ProviderTableMeta.FILE_PATH); + mProjectionMap.put(ProviderTableMeta.FILE_NAME, + ProviderTableMeta.FILE_NAME); + mProjectionMap.put(ProviderTableMeta.FILE_CREATION, + ProviderTableMeta.FILE_CREATION); + mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED, + ProviderTableMeta.FILE_MODIFIED); + mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA); + mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_LENGTH, + ProviderTableMeta.FILE_CONTENT_LENGTH); + mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_TYPE, + ProviderTableMeta.FILE_CONTENT_TYPE); + mProjectionMap.put(ProviderTableMeta.FILE_STORAGE_PATH, + ProviderTableMeta.FILE_STORAGE_PATH); + mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, + ProviderTableMeta.FILE_LAST_SYNC_DATE); + mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA); + mProjectionMap.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, + ProviderTableMeta.FILE_KEEP_IN_SYNC); + mProjectionMap.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, + ProviderTableMeta.FILE_ACCOUNT_OWNER); + } + + private static final int SINGLE_FILE = 1; + private static final int DIRECTORY = 2; + private static final int ROOT_DIRECTORY = 3; + + private UriMatcher mUriMatcher; +// static { +// mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); +// mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, null, ROOT_DIRECTORY); +// mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "file/", SINGLE_FILE); +// mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "file/#", SINGLE_FILE); +// mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "dir/#", DIRECTORY); +// } + + + @Override + public int delete(Uri uri, String where, String[] whereArgs) { + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + int count = 0; + switch (mUriMatcher.match(uri)) { + case SINGLE_FILE: + count = db.delete(ProviderTableMeta.DB_NAME, + ProviderTableMeta._ID + + "=" + + uri.getPathSegments().get(1) + + (!TextUtils.isEmpty(where) ? " AND (" + where + + ")" : ""), whereArgs); + break; + case ROOT_DIRECTORY: + count = db.delete(ProviderTableMeta.DB_NAME, where, whereArgs); + break; + default: + throw new IllegalArgumentException("Unknown uri: " + uri.toString()); + } + getContext().getContentResolver().notifyChange(uri, null); + return count; + } + + @Override + public String getType(Uri uri) { + switch (mUriMatcher.match(uri)) { + case ROOT_DIRECTORY: + return ProviderTableMeta.CONTENT_TYPE; + case SINGLE_FILE: + return ProviderTableMeta.CONTENT_TYPE_ITEM; + default: + throw new IllegalArgumentException("Unknown Uri id." + + uri.toString()); + } + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + if (mUriMatcher.match(uri) != SINGLE_FILE && + mUriMatcher.match(uri) != ROOT_DIRECTORY) { + + throw new IllegalArgumentException("Unknown uri id: " + uri); + } + + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + long rowId = db.insert(ProviderTableMeta.DB_NAME, null, values); + if (rowId > 0) { + Uri insertedFileUri = ContentUris.withAppendedId( + ProviderTableMeta.CONTENT_URI_FILE, rowId); + getContext().getContentResolver().notifyChange(insertedFileUri, + null); + return insertedFileUri; + } + throw new SQLException("ERROR " + uri); + } + + @Override + public boolean onCreate() { + mDbHelper = new DataBaseHelper(getContext()); + + String authority = getContext().getResources().getString(R.string.authority); + mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + mUriMatcher.addURI(authority, null, ROOT_DIRECTORY); + mUriMatcher.addURI(authority, "file/", SINGLE_FILE); + mUriMatcher.addURI(authority, "file/#", SINGLE_FILE); + mUriMatcher.addURI(authority, "dir/#", DIRECTORY); + + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + SQLiteQueryBuilder sqlQuery = new SQLiteQueryBuilder(); + + sqlQuery.setTables(ProviderTableMeta.DB_NAME); + sqlQuery.setProjectionMap(mProjectionMap); + + switch (mUriMatcher.match(uri)) { + case ROOT_DIRECTORY: + break; + case DIRECTORY: + sqlQuery.appendWhere(ProviderTableMeta.FILE_PARENT + "=" + + uri.getPathSegments().get(1)); + break; + case SINGLE_FILE: + if (uri.getPathSegments().size() > 1) { + sqlQuery.appendWhere(ProviderTableMeta._ID + "=" + + uri.getPathSegments().get(1)); + } + break; + default: + throw new IllegalArgumentException("Unknown uri id: " + uri); + } + + String order; + if (TextUtils.isEmpty(sortOrder)) { + order = ProviderTableMeta.DEFAULT_SORT_ORDER; + } else { + order = sortOrder; + } + + SQLiteDatabase db = mDbHelper.getReadableDatabase(); + // DB case_sensitive + db.execSQL("PRAGMA case_sensitive_like = true"); + Cursor c = sqlQuery.query(db, projection, selection, selectionArgs, + null, null, order); + + c.setNotificationUri(getContext().getContentResolver(), uri); + + return c; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, + String[] selectionArgs) { + return mDbHelper.getWritableDatabase().update( + ProviderTableMeta.DB_NAME, values, selection, selectionArgs); + } + + class DataBaseHelper extends SQLiteOpenHelper { + + public DataBaseHelper(Context context) { + super(context, ProviderMeta.DB_NAME, null, ProviderMeta.DB_VERSION); + + } + + @Override + public void onCreate(SQLiteDatabase db) { + // files table + Log_OC.i("SQL", "Entering in onCreate"); + db.execSQL("CREATE TABLE " + ProviderTableMeta.DB_NAME + "(" + + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, " + + ProviderTableMeta.FILE_NAME + " TEXT, " + + ProviderTableMeta.FILE_PATH + " TEXT, " + + ProviderTableMeta.FILE_PARENT + " INTEGER, " + + ProviderTableMeta.FILE_CREATION + " INTEGER, " + + ProviderTableMeta.FILE_MODIFIED + " INTEGER, " + + ProviderTableMeta.FILE_CONTENT_TYPE + " TEXT, " + + ProviderTableMeta.FILE_CONTENT_LENGTH + " INTEGER, " + + ProviderTableMeta.FILE_STORAGE_PATH + " TEXT, " + + ProviderTableMeta.FILE_ACCOUNT_OWNER + " TEXT, " + + ProviderTableMeta.FILE_LAST_SYNC_DATE + " INTEGER, " + + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER, " + + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER, " + + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER );" + ); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log_OC.i("SQL", "Entering in onUpgrade"); + boolean upgraded = false; + if (oldVersion == 1 && newVersion >= 2) { + Log_OC.i("SQL", "Entering in the #1 ADD in onUpgrade"); + db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME + + " ADD COLUMN " + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER " + + " DEFAULT 0"); + upgraded = true; + } + if (oldVersion < 3 && newVersion >= 3) { + Log_OC.i("SQL", "Entering in the #2 ADD in onUpgrade"); + db.beginTransaction(); + try { + db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME + + " ADD COLUMN " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER " + + " DEFAULT 0"); + + // assume there are not local changes pending to upload + db.execSQL("UPDATE " + ProviderTableMeta.DB_NAME + + " SET " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " = " + System.currentTimeMillis() + + " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL"); + + upgraded = true; + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + if (oldVersion < 4 && newVersion >= 4) { + Log_OC.i("SQL", "Entering in the #3 ADD in onUpgrade"); + db.beginTransaction(); + try { + db .execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME + + " ADD COLUMN " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER " + + " DEFAULT 0"); + + db.execSQL("UPDATE " + ProviderTableMeta.DB_NAME + + " SET " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " = " + ProviderTableMeta.FILE_MODIFIED + + " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL"); + + upgraded = true; + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + if (!upgraded) + Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); + } + + } + +} diff --git a/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java b/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java new file mode 100644 index 00000000..47730995 --- /dev/null +++ b/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java @@ -0,0 +1,154 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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.syncadapter; + +import java.io.IOException; +import java.util.Date; + +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.protocol.HttpContext; + +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.authentication.AccountUtils.AccountNotFoundException; +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.network.OwnCloudClientUtils; + + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.content.AbstractThreadedSyncAdapter; +import android.content.ContentProviderClient; +import android.content.Context; +import eu.alefzero.webdav.WebdavClient; + +/** + * Base SyncAdapter for OwnCloud Designed to be subclassed for the concrete + * SyncAdapter, like ConcatsSync, CalendarSync, FileSync etc.. + * + * @author sassman + * + */ +public abstract class AbstractOwnCloudSyncAdapter extends + AbstractThreadedSyncAdapter { + + private AccountManager accountManager; + private Account account; + private ContentProviderClient contentProvider; + private Date lastUpdated; + private DataStorageManager mStoreManager; + + private WebdavClient mClient = null; + + public AbstractOwnCloudSyncAdapter(Context context, boolean autoInitialize) { + super(context, autoInitialize); + this.setAccountManager(AccountManager.get(context)); + } + + public AccountManager getAccountManager() { + return accountManager; + } + + public void setAccountManager(AccountManager accountManager) { + this.accountManager = accountManager; + } + + public Account getAccount() { + return account; + } + + public void setAccount(Account account) { + this.account = account; + } + + public ContentProviderClient getContentProvider() { + return contentProvider; + } + + public void setContentProvider(ContentProviderClient contentProvider) { + this.contentProvider = contentProvider; + } + + public Date getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(Date lastUpdated) { + this.lastUpdated = lastUpdated; + } + + public void setStorageManager(DataStorageManager storage_manager) { + mStoreManager = storage_manager; + } + + public DataStorageManager getStorageManager() { + return mStoreManager; + } + + protected ConnectionKeepAliveStrategy getKeepAliveStrategy() { + return new ConnectionKeepAliveStrategy() { + public long getKeepAliveDuration(HttpResponse response, + HttpContext context) { + // Change keep alive straategy basing on response: ie + // forbidden/not found/etc + // should have keep alive 0 + // default return: 5s + int statusCode = response.getStatusLine().getStatusCode(); + + // HTTP 400, 500 Errors as well as HTTP 118 - Connection timed + // out + if ((statusCode >= 400 && statusCode <= 418) + || (statusCode >= 421 && statusCode <= 426) + || (statusCode >= 500 && statusCode <= 510) + || statusCode == 118) { + return 0; + } + + return 5 * 1000; + } + }; + } + + protected HttpResponse fireRawRequest(HttpRequest query) + throws ClientProtocolException, OperationCanceledException, + AuthenticatorException, IOException { + /* + * BasicHttpContext httpContext = new BasicHttpContext(); BasicScheme + * basicAuth = new BasicScheme(); + * httpContext.setAttribute("preemptive-auth", basicAuth); + * + * HttpResponse response = getClient().execute(mHost, query, + * httpContext); + */ + return null; + } + + protected void initClientForCurrentAccount() throws OperationCanceledException, AuthenticatorException, IOException, AccountNotFoundException { + AccountUtils.constructFullURLForAccount(getContext(), account); + mClient = OwnCloudClientUtils.createOwnCloudClient(account, getContext()); + } + + protected WebdavClient getClient() { + return mClient; + } +} \ No newline at end of file diff --git a/src/com/owncloud/android/syncadapter/ContactSyncAdapter.java b/src/com/owncloud/android/syncadapter/ContactSyncAdapter.java new file mode 100644 index 00000000..b07c048a --- /dev/null +++ b/src/com/owncloud/android/syncadapter/ContactSyncAdapter.java @@ -0,0 +1,123 @@ +/* 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.syncadapter; + +import java.io.FileInputStream; +import java.io.IOException; + +import org.apache.http.client.methods.HttpPut; +import org.apache.http.entity.ByteArrayEntity; + +import com.owncloud.android.authentication.AccountAuthenticator; +import com.owncloud.android.authentication.AccountUtils; + + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.content.ContentProviderClient; +import android.content.Context; +import android.content.SyncResult; +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.ContactsContract; + +public class ContactSyncAdapter extends AbstractOwnCloudSyncAdapter { + private String mAddrBookUri; + + public ContactSyncAdapter(Context context, boolean autoInitialize) { + super(context, autoInitialize); + mAddrBookUri = null; + } + + @Override + public void onPerformSync(Account account, Bundle extras, String authority, + ContentProviderClient provider, SyncResult syncResult) { + setAccount(account); + setContentProvider(provider); + Cursor c = getLocalContacts(false); + if (c.moveToFirst()) { + do { + String lookup = c.getString(c + .getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)); + String a = getAddressBookUri(); + String uri = a + lookup + ".vcf"; + FileInputStream f; + try { + f = getContactVcard(lookup); + HttpPut query = new HttpPut(uri); + byte[] b = new byte[f.available()]; + f.read(b); + query.setEntity(new ByteArrayEntity(b)); + fireRawRequest(query); + } catch (IOException e) { + e.printStackTrace(); + return; + } catch (OperationCanceledException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (AuthenticatorException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } while (c.moveToNext()); + // } while (c.moveToNext()); + } + + } + + private String getAddressBookUri() { + if (mAddrBookUri != null) + return mAddrBookUri; + + AccountManager am = getAccountManager(); + @SuppressWarnings("deprecation") + String uri = am.getUserData(getAccount(), + AccountAuthenticator.KEY_OC_URL).replace( + AccountUtils.WEBDAV_PATH_2_0, AccountUtils.CARDDAV_PATH_2_0); + uri += "/addressbooks/" + + getAccount().name.substring(0, + getAccount().name.lastIndexOf('@')) + "/default/"; + mAddrBookUri = uri; + return uri; + } + + private FileInputStream getContactVcard(String lookupKey) + throws IOException { + Uri uri = Uri.withAppendedPath( + ContactsContract.Contacts.CONTENT_VCARD_URI, lookupKey); + AssetFileDescriptor fd = getContext().getContentResolver() + .openAssetFileDescriptor(uri, "r"); + return fd.createInputStream(); + } + + private Cursor getLocalContacts(boolean include_hidden_contacts) { + return getContext().getContentResolver().query( + ContactsContract.Contacts.CONTENT_URI, + new String[] { ContactsContract.Contacts._ID, + ContactsContract.Contacts.LOOKUP_KEY }, + ContactsContract.Contacts.IN_VISIBLE_GROUP + " = ?", + new String[] { (include_hidden_contacts ? "0" : "1") }, + ContactsContract.Contacts._ID + " DESC"); + } + +} diff --git a/src/com/owncloud/android/syncadapter/ContactSyncService.java b/src/com/owncloud/android/syncadapter/ContactSyncService.java new file mode 100644 index 00000000..6d7c46c0 --- /dev/null +++ b/src/com/owncloud/android/syncadapter/ContactSyncService.java @@ -0,0 +1,44 @@ +/* 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.syncadapter; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +public class ContactSyncService extends Service { + private static final Object syncAdapterLock = new Object(); + private static AbstractOwnCloudSyncAdapter mSyncAdapter = null; + + @Override + public void onCreate() { + synchronized (syncAdapterLock) { + if (mSyncAdapter == null) { + mSyncAdapter = new ContactSyncAdapter(getApplicationContext(), + true); + } + } + } + + @Override + public IBinder onBind(Intent arg0) { + return mSyncAdapter.getSyncAdapterBinder(); + } + +} diff --git a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java new file mode 100644 index 00000000..8c187cc6 --- /dev/null +++ b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java @@ -0,0 +1,410 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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.syncadapter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.jackrabbit.webdav.DavException; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AuthenticatorActivity; +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.operations.SynchronizeFolderOperation; +import com.owncloud.android.operations.UpdateOCVersionOperation; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.ui.activity.ErrorsWhileCopyingHandlerActivity; + + +import android.accounts.Account; +import android.accounts.AccountsException; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.SyncResult; +import android.os.Bundle; + +/** + * SyncAdapter implementation for syncing sample SyncAdapter contacts to the + * platform ContactOperations provider. + * + * @author Bartek Przybylski + * @author David A. Velasco + */ +public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { + + private final static String TAG = "FileSyncAdapter"; + + /** + * Maximum number of failed folder synchronizations that are supported before finishing the synchronization operation + */ + private static final int MAX_FAILED_RESULTS = 3; + + private long mCurrentSyncTime; + private boolean mCancellation; + private boolean mIsManualSync; + private int mFailedResultsCounter; + private RemoteOperationResult mLastFailedResult; + private SyncResult mSyncResult; + private int mConflictsFound; + private int mFailsInFavouritesFound; + private Map mForgottenLocalFiles; + + + public FileSyncAdapter(Context context, boolean autoInitialize) { + super(context, autoInitialize); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void onPerformSync(Account account, Bundle extras, + String authority, ContentProviderClient provider, + SyncResult syncResult) { + + mCancellation = false; + mIsManualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); + mFailedResultsCounter = 0; + mLastFailedResult = null; + mConflictsFound = 0; + mFailsInFavouritesFound = 0; + mForgottenLocalFiles = new HashMap(); + mSyncResult = syncResult; + mSyncResult.fullSyncRequested = false; + mSyncResult.delayUntil = 60*60*24; // sync after 24h + + this.setAccount(account); + this.setContentProvider(provider); + this.setStorageManager(new FileDataStorageManager(account, getContentProvider())); + try { + this.initClientForCurrentAccount(); + } catch (IOException e) { + /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again + mSyncResult.tooManyRetries = true; + notifyFailedSynchronization(); + return; + } catch (AccountsException e) { + /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again + mSyncResult.tooManyRetries = true; + notifyFailedSynchronization(); + return; + } + + Log_OC.d(TAG, "Synchronization of ownCloud account " + account.name + " starting"); + sendStickyBroadcast(true, null, null); // message to signal the start of the synchronization to the UI + + try { + updateOCVersion(); + mCurrentSyncTime = System.currentTimeMillis(); + if (!mCancellation) { + fetchData(OCFile.PATH_SEPARATOR, DataStorageManager.ROOT_PARENT_ID); + + } else { + Log_OC.d(TAG, "Leaving synchronization before any remote request due to cancellation was requested"); + } + + + } finally { + // it's important making this although very unexpected errors occur; that's the reason for the finally + + if (mFailedResultsCounter > 0 && mIsManualSync) { + /// don't let the system synchronization manager retries MANUAL synchronizations + // (be careful: "MANUAL" currently includes the synchronization requested when a new account is created and when the user changes the current account) + mSyncResult.tooManyRetries = true; + + /// notify the user about the failure of MANUAL synchronization + notifyFailedSynchronization(); + + } + if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) { + notifyFailsInFavourites(); + } + if (mForgottenLocalFiles.size() > 0) { + notifyForgottenLocalFiles(); + + } + sendStickyBroadcast(false, null, mLastFailedResult); // message to signal the end to the UI + } + + } + + /** + * Called by system SyncManager when a synchronization is required to be cancelled. + * + * Sets the mCancellation flag to 'true'. THe synchronization will be stopped when before a new folder is fetched. Data of the last folder + * fetched will be still saved in the database. See onPerformSync implementation. + */ + @Override + public void onSyncCanceled() { + Log_OC.d(TAG, "Synchronization of " + getAccount().name + " has been requested to cancel"); + mCancellation = true; + super.onSyncCanceled(); + } + + + /** + * Updates the locally stored version value of the ownCloud server + */ + private void updateOCVersion() { + UpdateOCVersionOperation update = new UpdateOCVersionOperation(getAccount(), getContext()); + RemoteOperationResult result = update.execute(getClient()); + if (!result.isSuccess()) { + mLastFailedResult = result; + } + } + + + /** + * Synchronize the properties of files and folders contained in a remote folder given by remotePath. + * + * @param remotePath Remote path to the folder to synchronize. + * @param parentId Database Id of the folder to synchronize. + */ + private void fetchData(String remotePath, long parentId) { + + if (mFailedResultsCounter > MAX_FAILED_RESULTS || isFinisher(mLastFailedResult)) + return; + + // perform folder synchronization + SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation( remotePath, + mCurrentSyncTime, + parentId, + getStorageManager(), + getAccount(), + getContext() + ); + RemoteOperationResult result = synchFolderOp.execute(getClient()); + + + // synchronized folder -> notice to UI - ALWAYS, although !result.isSuccess + sendStickyBroadcast(true, remotePath, null); + + if (result.isSuccess() || result.getCode() == ResultCode.SYNC_CONFLICT) { + + if (result.getCode() == ResultCode.SYNC_CONFLICT) { + mConflictsFound += synchFolderOp.getConflictsFound(); + mFailsInFavouritesFound += synchFolderOp.getFailsInFavouritesFound(); + } + if (synchFolderOp.getForgottenLocalFiles().size() > 0) { + mForgottenLocalFiles.putAll(synchFolderOp.getForgottenLocalFiles()); + } + // synchronize children folders + List children = synchFolderOp.getChildren(); + fetchChildren(children); // beware of the 'hidden' recursion here! + + sendStickyBroadcast(true, remotePath, null); + + } else { + if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED || + // (result.isTemporalRedirection() && result.isIdPRedirection() && + ( result.isIdPRedirection() && + MainApp.getAuthTokenTypeSamlSessionCookie().equals(getClient().getAuthTokenType()))) { + mSyncResult.stats.numAuthExceptions++; + + } else if (result.getException() instanceof DavException) { + mSyncResult.stats.numParseExceptions++; + + } else if (result.getException() instanceof IOException) { + mSyncResult.stats.numIoExceptions++; + } + mFailedResultsCounter++; + mLastFailedResult = result; + } + + } + + /** + * Checks if a failed result should terminate the synchronization process immediately, according to + * OUR OWN POLICY + * + * @param failedResult Remote operation result to check. + * @return 'True' if the result should immediately finish the synchronization + */ + private boolean isFinisher(RemoteOperationResult failedResult) { + if (failedResult != null) { + RemoteOperationResult.ResultCode code = failedResult.getCode(); + return (code.equals(RemoteOperationResult.ResultCode.SSL_ERROR) || + code.equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) || + code.equals(RemoteOperationResult.ResultCode.BAD_OC_VERSION) || + code.equals(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED)); + } + return false; + } + + /** + * Synchronize data of folders in the list of received files + * + * @param files Files to recursively fetch + */ + private void fetchChildren(List files) { + int i; + for (i=0; i < files.size() && !mCancellation; i++) { + OCFile newFile = files.get(i); + if (newFile.isDirectory()) { + fetchData(newFile.getRemotePath(), newFile.getFileId()); + + // Update folder size on DB + getStorageManager().calculateFolderSize(newFile.getFileId()); + } + } + + if (mCancellation && i 0) { + Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_fail_in_favourites_ticker), System.currentTimeMillis()); + notification.flags |= Notification.FLAG_AUTO_CANCEL; + // TODO put something smart in the contentIntent below + notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); + notification.setLatestEventInfo(getContext().getApplicationContext(), + getContext().getString(R.string.sync_fail_in_favourites_ticker), + String.format(getContext().getString(R.string.sync_fail_in_favourites_content), mFailedResultsCounter + mConflictsFound, mConflictsFound), + notification.contentIntent); + ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_in_favourites_ticker, notification); + + } else { + Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_conflicts_in_favourites_ticker), System.currentTimeMillis()); + notification.flags |= Notification.FLAG_AUTO_CANCEL; + // TODO put something smart in the contentIntent below + notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); + notification.setLatestEventInfo(getContext().getApplicationContext(), + getContext().getString(R.string.sync_conflicts_in_favourites_ticker), + String.format(getContext().getString(R.string.sync_conflicts_in_favourites_content), mConflictsFound), + notification.contentIntent); + ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_conflicts_in_favourites_ticker, notification); + } + } + + /** + * Notifies the user about local copies of files out of the ownCloud local directory that were 'forgotten' because + * copying them inside the ownCloud local directory was not possible. + * + * We don't want links to files out of the ownCloud local directory (foreign files) anymore. It's easy to have + * synchronization problems if a local file is linked to more than one remote file. + * + * We won't consider a synchronization as failed when foreign files can not be copied to the ownCloud local directory. + */ + private void notifyForgottenLocalFiles() { + Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_foreign_files_forgotten_ticker), System.currentTimeMillis()); + notification.flags |= Notification.FLAG_AUTO_CANCEL; + + /// includes a pending intent in the notification showing a more detailed explanation + Intent explanationIntent = new Intent(getContext(), ErrorsWhileCopyingHandlerActivity.class); + explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_ACCOUNT, getAccount()); + ArrayList remotePaths = new ArrayList(); + ArrayList localPaths = new ArrayList(); + remotePaths.addAll(mForgottenLocalFiles.keySet()); + localPaths.addAll(mForgottenLocalFiles.values()); + explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_LOCAL_PATHS, localPaths); + explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_REMOTE_PATHS, remotePaths); + explanationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + + notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), explanationIntent, 0); + notification.setLatestEventInfo(getContext().getApplicationContext(), + getContext().getString(R.string.sync_foreign_files_forgotten_ticker), + String.format(getContext().getString(R.string.sync_foreign_files_forgotten_content), mForgottenLocalFiles.size(), getContext().getString(R.string.app_name)), + notification.contentIntent); + ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_foreign_files_forgotten_ticker, notification); + + } + + +} diff --git a/src/com/owncloud/android/syncadapter/FileSyncService.java b/src/com/owncloud/android/syncadapter/FileSyncService.java new file mode 100644 index 00000000..d3472658 --- /dev/null +++ b/src/com/owncloud/android/syncadapter/FileSyncService.java @@ -0,0 +1,54 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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.syncadapter; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +/** + * Background service for syncing files to our local Database + * + * @author Bartek Przybylski + * + */ +public class FileSyncService extends Service { + public static final String SYNC_MESSAGE = "ACCOUNT_SYNC"; + public static final String SYNC_FOLDER_REMOTE_PATH = "SYNC_FOLDER_REMOTE_PATH"; + public static final String IN_PROGRESS = "SYNC_IN_PROGRESS"; + public static final String ACCOUNT_NAME = "ACCOUNT_NAME"; + public static final String SYNC_RESULT = "SYNC_RESULT"; + + public String getSyncMessage(){ + return getClass().getName().toString() + SYNC_MESSAGE; + } + /* + * {@inheritDoc} + */ + @Override + public void onCreate() { + } + + /* + * {@inheritDoc} + */ + @Override + public IBinder onBind(Intent intent) { + return new FileSyncAdapter(getApplicationContext(), true).getSyncAdapterBinder(); + } +} diff --git a/src/com/owncloud/android/ui/ActionItem.java b/src/com/owncloud/android/ui/ActionItem.java new file mode 100644 index 00000000..a65f3ad0 --- /dev/null +++ b/src/com/owncloud/android/ui/ActionItem.java @@ -0,0 +1,61 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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; + +import android.graphics.drawable.Drawable; +import android.view.View.OnClickListener; + +/** + * Represents an Item on the ActionBar. + * + * @author Bartek Przybylski + * + */ +public class ActionItem { + private Drawable mIcon; + private String mTitle; + private OnClickListener mClickListener; + + public ActionItem() { + } + + public void setTitle(String title) { + mTitle = title; + } + + public String getTitle() { + return mTitle; + } + + public void setIcon(Drawable icon) { + mIcon = icon; + } + + public Drawable getIcon() { + return mIcon; + } + + public void setOnClickListener(OnClickListener listener) { + mClickListener = listener; + } + + public OnClickListener getOnClickListerner() { + return mClickListener; + } + +} diff --git a/src/com/owncloud/android/ui/CustomButton.java b/src/com/owncloud/android/ui/CustomButton.java new file mode 100644 index 00000000..ab4db65d --- /dev/null +++ b/src/com/owncloud/android/ui/CustomButton.java @@ -0,0 +1,46 @@ +package com.owncloud.android.ui; + +import com.owncloud.android.R; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.Button; +/** + * @author masensio + * + * Button for customizing the button background + */ + +public class CustomButton extends Button { + + public CustomButton(Context context) { + super(context); + + boolean customButtons = getResources().getBoolean(R.bool.custom_buttons); + if (customButtons) + { + this.setBackgroundResource(R.drawable.btn_default); + } + } + + public CustomButton(Context context, AttributeSet attrs) { + super(context, attrs); + + boolean customButtons = getResources().getBoolean(R.bool.custom_buttons); + if (customButtons) + { + this.setBackgroundResource(R.drawable.btn_default); + } + } + + public CustomButton(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + boolean customButtons = getResources().getBoolean(R.bool.custom_buttons); + if (customButtons) + { + this.setBackgroundResource(R.drawable.btn_default); + } + } + +} diff --git a/src/com/owncloud/android/ui/CustomPopup.java b/src/com/owncloud/android/ui/CustomPopup.java new file mode 100644 index 00000000..fccf56d2 --- /dev/null +++ b/src/com/owncloud/android/ui/CustomPopup.java @@ -0,0 +1,154 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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; + +import android.content.Context; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.View.OnTouchListener; +import android.view.ViewGroup.LayoutParams; +import android.widget.PopupWindow; + +/** + * Represents a custom PopupWindows + * + * @author Lorensius. W. T + * + */ +public class CustomPopup { + protected final View mAnchor; + protected final PopupWindow mWindow; + private View root; + private Drawable background = null; + protected final WindowManager mWManager; + + public CustomPopup(View anchor) { + mAnchor = anchor; + mWindow = new PopupWindow(anchor.getContext()); + + mWindow.setTouchInterceptor(new OnTouchListener() { + + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { + CustomPopup.this.dismiss(); + return true; + } + return false; + } + }); + + mWManager = (WindowManager) anchor.getContext().getSystemService( + Context.WINDOW_SERVICE); + onCreate(); + } + + public void onCreate() { + } + + public void onShow() { + } + + public void preShow() { + if (root == null) { + throw new IllegalStateException( + "setContentView called with a view to display"); + } + + onShow(); + + if (background == null) { + mWindow.setBackgroundDrawable(new BitmapDrawable()); + } else { + mWindow.setBackgroundDrawable(background); + } + + mWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT); + mWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT); + mWindow.setTouchable(true); + mWindow.setFocusable(true); + mWindow.setOutsideTouchable(true); + + mWindow.setContentView(root); + } + + public void setBackgroundDrawable(Drawable background) { + this.background = background; + } + + public void setContentView(View root) { + this.root = root; + mWindow.setContentView(root); + } + + public void setContentView(int layoutResId) { + LayoutInflater inflater = (LayoutInflater) mAnchor.getContext() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + setContentView(inflater.inflate(layoutResId, null)); + } + + public void showDropDown() { + showDropDown(0, 0); + } + + public void showDropDown(int x, int y) { + preShow(); + mWindow.setAnimationStyle(android.R.style.Animation_Dialog); + mWindow.showAsDropDown(mAnchor, x, y); + } + + public void showLikeQuickAction() { + showLikeQuickAction(0, 0); + } + + public void showLikeQuickAction(int x, int y) { + preShow(); + + mWindow.setAnimationStyle(android.R.style.Animation_Dialog); + int[] location = new int[2]; + mAnchor.getLocationOnScreen(location); + + Rect anchorRect = new Rect(location[0], location[1], location[0] + + mAnchor.getWidth(), location[1] + mAnchor.getHeight()); + + root.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT)); + root.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + + int rootW = root.getWidth(), rootH = root.getHeight(); + int screenW = mWManager.getDefaultDisplay().getWidth(); + + int xpos = ((screenW - rootW) / 2) + x; + int ypos = anchorRect.top - rootH + y; + + if (rootH > anchorRect.top) { + ypos = anchorRect.bottom + y; + } + mWindow.showAtLocation(mAnchor, Gravity.NO_GRAVITY, xpos, ypos); + } + + public void dismiss() { + mWindow.dismiss(); + } + +} diff --git a/src/com/owncloud/android/ui/ExtendedListView.java b/src/com/owncloud/android/ui/ExtendedListView.java new file mode 100644 index 00000000..9fe885bf --- /dev/null +++ b/src/com/owncloud/android/ui/ExtendedListView.java @@ -0,0 +1,73 @@ +/* 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; + +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.widget.ListView; + +/** + * ListView allowing to specify the position of an item that should be centered in the visible area, if possible. + * + * The cleanest way I found to overcome the problem due to getHeight() returns 0 until the view is really drawn. + * + * @author David A. Velasco + */ +public class ExtendedListView extends ListView { + + private int mPositionToSetAndCenter; + + public ExtendedListView(Context context) { + super(context); + } + + public ExtendedListView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ExtendedListView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * {@inheritDoc} + * + * + */ + @Override + protected void onDraw (Canvas canvas) { + super.onDraw(canvas); + if (mPositionToSetAndCenter > 0) { + this.setSelectionFromTop(mPositionToSetAndCenter, getHeight() / 2); + mPositionToSetAndCenter = 0; + } + } + + /** + * Public method to set the position of the item that should be centered in the visible area of the view. + * + * The position is saved here and checked in onDraw(). + * + * @param position Position (in the list of items) of the item to center in the visible area. + */ + public void setAndCenterSelection(int position) { + mPositionToSetAndCenter = position; + } +} diff --git a/src/com/owncloud/android/ui/QuickAction.java b/src/com/owncloud/android/ui/QuickAction.java new file mode 100644 index 00000000..86fe3fe3 --- /dev/null +++ b/src/com/owncloud/android/ui/QuickAction.java @@ -0,0 +1,306 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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; + +import android.content.Context; + +import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.LinearLayout; +import android.widget.ScrollView; + +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup.LayoutParams; +import android.view.ViewGroup; + +import java.util.ArrayList; + +import com.owncloud.android.R; + + +/** + * Popup window, shows action list as icon and text like the one in Gallery3D + * app. + * + * @author Lorensius. W. T + */ +public class QuickAction extends CustomPopup { + private final View root; + private final ImageView mArrowUp; + private final ImageView mArrowDown; + private final LayoutInflater inflater; + private final Context context; + + protected static final int ANIM_GROW_FROM_LEFT = 1; + protected static final int ANIM_GROW_FROM_RIGHT = 2; + protected static final int ANIM_GROW_FROM_CENTER = 3; + protected static final int ANIM_REFLECT = 4; + protected static final int ANIM_AUTO = 5; + + private int animStyle; + private ViewGroup mTrack; + private ScrollView scroller; + private ArrayList actionList; + + /** + * Constructor + * + * @param anchor {@link View} on where the popup window should be displayed + */ + public QuickAction(View anchor) { + super(anchor); + + actionList = new ArrayList(); + context = anchor.getContext(); + inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + root = (ViewGroup) inflater.inflate(R.layout.popup, null); + + mArrowDown = (ImageView) root.findViewById(R.id.arrow_down); + mArrowUp = (ImageView) root.findViewById(R.id.arrow_up); + + setContentView(root); + + mTrack = (ViewGroup) root.findViewById(R.id.tracks); + scroller = (ScrollView) root.findViewById(R.id.scroller); + animStyle = ANIM_AUTO; + } + + /** + * Set animation style + * + * @param animStyle animation style, default is set to ANIM_AUTO + */ + public void setAnimStyle(int animStyle) { + this.animStyle = animStyle; + } + + /** + * Add action item + * + * @param action {@link ActionItem} object + */ + public void addActionItem(ActionItem action) { + actionList.add(action); + } + + /** + * Show popup window. Popup is automatically positioned, on top or bottom of + * anchor view. + * + */ + public void show() { + preShow(); + + int xPos, yPos; + + int[] location = new int[2]; + + mAnchor.getLocationOnScreen(location); + + Rect anchorRect = new Rect(location[0], location[1], location[0] + + mAnchor.getWidth(), location[1] + mAnchor.getHeight()); + + createActionList(); + + root.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT)); + root.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + + int rootHeight = root.getMeasuredHeight(); + int rootWidth = root.getMeasuredWidth(); + + int screenWidth = mWManager.getDefaultDisplay().getWidth(); + int screenHeight = mWManager.getDefaultDisplay().getHeight(); + + // automatically get X coord of popup (top left) + if ((anchorRect.left + rootWidth) > screenWidth) { + xPos = anchorRect.left - (rootWidth - mAnchor.getWidth()); + } else { + if (mAnchor.getWidth() > rootWidth) { + xPos = anchorRect.centerX() - (rootWidth / 2); + } else { + xPos = anchorRect.left; + } + } + + int dyTop = anchorRect.top; + int dyBottom = screenHeight - anchorRect.bottom; + + boolean onTop = (dyTop > dyBottom) ? true : false; + + if (onTop) { + if (rootHeight > dyTop) { + yPos = 15; + LayoutParams l = scroller.getLayoutParams(); + l.height = dyTop - mAnchor.getHeight(); + } else { + yPos = anchorRect.top - rootHeight; + } + } else { + yPos = anchorRect.bottom; + + if (rootHeight > dyBottom) { + LayoutParams l = scroller.getLayoutParams(); + l.height = dyBottom; + } + } + + showArrow(((onTop) ? R.id.arrow_down : R.id.arrow_up), + anchorRect.centerX() - xPos); + + setAnimationStyle(screenWidth, anchorRect.centerX(), onTop); + + mWindow.showAtLocation(mAnchor, Gravity.NO_GRAVITY, xPos, yPos); + } + + /** + * Set animation style + * + * @param screenWidth screen width + * @param requestedX distance from left edge + * @param onTop flag to indicate where the popup should be displayed. Set + * TRUE if displayed on top of anchor view and vice versa + */ + private void setAnimationStyle(int screenWidth, int requestedX, + boolean onTop) { + int arrowPos = requestedX - mArrowUp.getMeasuredWidth() / 2; + + switch (animStyle) { + case ANIM_GROW_FROM_LEFT: + mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Left + : R.style.Animations_PopDownMenu_Left); + break; + + case ANIM_GROW_FROM_RIGHT: + mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Right + : R.style.Animations_PopDownMenu_Right); + break; + + case ANIM_GROW_FROM_CENTER: + mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Center + : R.style.Animations_PopDownMenu_Center); + break; + + case ANIM_REFLECT: + mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Reflect + : R.style.Animations_PopDownMenu_Reflect); + break; + + case ANIM_AUTO: + if (arrowPos <= screenWidth / 4) { + mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Left + : R.style.Animations_PopDownMenu_Left); + } else if (arrowPos > screenWidth / 4 + && arrowPos < 3 * (screenWidth / 4)) { + mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Center + : R.style.Animations_PopDownMenu_Center); + } else { + mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Right + : R.style.Animations_PopDownMenu_Right); + } + + break; + } + } + + /** + * Create action list + */ + private void createActionList() { + View view; + String title; + Drawable icon; + OnClickListener listener; + + for (int i = 0; i < actionList.size(); i++) { + title = actionList.get(i).getTitle(); + icon = actionList.get(i).getIcon(); + listener = actionList.get(i).getOnClickListerner(); + + view = getActionItem(title, icon, listener); + + view.setFocusable(true); + view.setClickable(true); + + mTrack.addView(view); + } + } + + /** + * Get action item {@link View} + * + * @param title action item title + * @param icon {@link Drawable} action item icon + * @param listener {@link View.OnClickListener} action item listener + * @return action item {@link View} + */ + private View getActionItem(String title, Drawable icon, + OnClickListener listener) { + LinearLayout container = (LinearLayout) inflater.inflate( + R.layout.action_item, null); + + ImageView img = (ImageView) container.findViewById(R.id.icon); + TextView text = (TextView) container.findViewById(R.id.title); + + if (icon != null) { + img.setImageDrawable(icon); + } + + if (title != null) { + text.setText(title); + } + + if (listener != null) { + container.setOnClickListener(listener); + } + + return container; + } + + /** + * Show arrow + * + * @param whichArrow arrow type resource id + * @param requestedX distance from left screen + */ + private void showArrow(int whichArrow, int requestedX) { + final View showArrow = (whichArrow == R.id.arrow_up) ? mArrowUp + : mArrowDown; + final View hideArrow = (whichArrow == R.id.arrow_up) ? mArrowDown + : mArrowUp; + + final int arrowWidth = mArrowUp.getMeasuredWidth(); + + showArrow.setVisibility(View.VISIBLE); + + ViewGroup.MarginLayoutParams param = (ViewGroup.MarginLayoutParams) showArrow + .getLayoutParams(); + + param.leftMargin = requestedX - arrowWidth / 2; + + hideArrow.setVisibility(View.INVISIBLE); + } +} \ No newline at end of file diff --git a/src/com/owncloud/android/ui/activity/AccountSelectActivity.java b/src/com/owncloud/android/ui/activity/AccountSelectActivity.java new file mode 100644 index 00000000..80e38f79 --- /dev/null +++ b/src/com/owncloud/android/ui/activity/AccountSelectActivity.java @@ -0,0 +1,274 @@ +/* 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.activity; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.view.ContextMenu; +import android.view.View; +import android.view.ViewGroup; +import android.view.ContextMenu.ContextMenuInfo; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.CheckedTextView; +import android.widget.ListView; +import android.widget.SimpleAdapter; +import android.widget.TextView; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockListActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; +import com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountAuthenticator; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.authentication.AuthenticatorActivity; + + +public class AccountSelectActivity extends SherlockListActivity implements + AccountManagerCallback { + + private static final String TAG = "AccountSelectActivity"; + + private static final String PREVIOUS_ACCOUNT_KEY = "ACCOUNT"; + + private final Handler mHandler = new Handler(); + private Account mPreviousAccount = null; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState != null) { + mPreviousAccount = savedInstanceState.getParcelable(PREVIOUS_ACCOUNT_KEY); + } else { + mPreviousAccount = AccountUtils.getCurrentOwnCloudAccount(this); + } + + ActionBar action_bar = getSupportActionBar(); + action_bar.setDisplayShowTitleEnabled(true); + action_bar.setDisplayHomeAsUpEnabled(false); + } + + @Override + protected void onResume() { + super.onResume(); + populateAccountList(); + } + + @Override + protected void onPause() { + super.onPause(); + if (this.isFinishing()) { + Account current = AccountUtils.getCurrentOwnCloudAccount(this); + if ((mPreviousAccount == null && current != null) || + (mPreviousAccount != null && !mPreviousAccount.equals(current))) { + /// the account set as default changed since this activity was created + + // trigger synchronization + ContentResolver.cancelSync(null, MainApp.getAuthTokenType()); + Bundle bundle = new Bundle(); + bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + ContentResolver.requestSync(AccountUtils.getCurrentOwnCloudAccount(this), MainApp.getAuthTokenType(), bundle); + + // restart the main activity + Intent i = new Intent(this, FileDisplayActivity.class); + i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(i); + } + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Show Create Account if Multiaccount is enabled + if (getResources().getBoolean(R.bool.multiaccount_support)) { + MenuInflater inflater = getSherlock().getMenuInflater(); + inflater.inflate(R.menu.account_picker, menu); + } + return true; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + getMenuInflater().inflate(R.menu.account_picker_long_click, menu); + super.onCreateContextMenu(menu, v, menuInfo); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + String accountName = ((TextView) v.findViewById(android.R.id.text1)) + .getText().toString(); + AccountUtils.setCurrentOwnCloudAccount(this, accountName); + finish(); // immediate exit + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + if (item.getItemId() == R.id.createAccount) { + /*Intent intent = new Intent( + android.provider.Settings.ACTION_ADD_ACCOUNT); + intent.putExtra("authorities", + new String[] { MainApp.getAuthTokenType() }); + startActivity(intent);*/ + AccountManager am = AccountManager.get(getApplicationContext()); + am.addAccount(MainApp.getAccountType(), + null, + null, + null, + this, + null, + null); + return true; + } + return false; + } + + /** + * Called when the user clicked on an item into the context menu created at + * {@link #onCreateContextMenu(ContextMenu, View, ContextMenuInfo)} for every + * ownCloud {@link Account} , containing 'secondary actions' for them. + * + * {@inheritDoc}} + */ + @SuppressWarnings("unchecked") + @Override + public boolean onContextItemSelected(android.view.MenuItem item) { + AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); + int index = info.position; + HashMap map = null; + try { + map = (HashMap) getListAdapter().getItem(index); + } catch (ClassCastException e) { + Log_OC.wtf(TAG, "getitem(index) from list adapter did not return hashmap, bailing out"); + return false; + } + + String accountName = map.get("NAME"); + AccountManager am = (AccountManager) getSystemService(ACCOUNT_SERVICE); + Account accounts[] = am.getAccountsByType(MainApp.getAccountType()); + for (Account a : accounts) { + if (a.name.equals(accountName)) { + if (item.getItemId() == R.id.change_password) { + Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class); + updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, a); + updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_TOKEN); + startActivity(updateAccountCredentials); + + } else if (item.getItemId() == R.id.delete_account) { + am.removeAccount(a, this, mHandler); + } + } + } + + return true; + } + + private void populateAccountList() { + AccountManager am = (AccountManager) getSystemService(ACCOUNT_SERVICE); + Account accounts[] = am + .getAccountsByType(MainApp.getAccountType()); + if (am.getAccountsByType(MainApp.getAccountType()).length == 0) { + // Show create account screen if there isn't any account + am.addAccount(MainApp.getAccountType(), + null, + null, + null, + this, + null, + null); + } + else { + LinkedList> ll = new LinkedList>(); + for (Account a : accounts) { + HashMap h = new HashMap(); + h.put("NAME", a.name); + h.put("VER", + "ownCloud version: " + + am.getUserData(a, + AccountAuthenticator.KEY_OC_VERSION)); + ll.add(h); + } + + setListAdapter(new AccountCheckedSimpleAdepter(this, ll, + android.R.layout.simple_list_item_single_choice, + new String[] { "NAME" }, new int[] { android.R.id.text1 })); + registerForContextMenu(getListView()); + } + } + + @Override + public void run(AccountManagerFuture future) { + if (future.isDone()) { + Account a = AccountUtils.getCurrentOwnCloudAccount(this); + String accountName = ""; + if (a == null) { + Account[] accounts = AccountManager.get(this) + .getAccountsByType(MainApp.getAccountType()); + if (accounts.length != 0) + accountName = accounts[0].name; + AccountUtils.setCurrentOwnCloudAccount(this, accountName); + } + populateAccountList(); + } + } + + private class AccountCheckedSimpleAdepter extends SimpleAdapter { + private Account mCurrentAccount; + private List> mPrivateData; + + public AccountCheckedSimpleAdepter(Context context, + List> data, int resource, + String[] from, int[] to) { + super(context, data, resource, from, to); + mCurrentAccount = AccountUtils + .getCurrentOwnCloudAccount(AccountSelectActivity.this); + mPrivateData = data; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View v = super.getView(position, convertView, parent); + CheckedTextView ctv = (CheckedTextView) v + .findViewById(android.R.id.text1); + if (mPrivateData.get(position).get("NAME") + .equals(mCurrentAccount.name)) { + ctv.setChecked(true); + } + return v; + } + + } + +} diff --git a/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java new file mode 100644 index 00000000..61ac8f5f --- /dev/null +++ b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java @@ -0,0 +1,103 @@ +/* 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.activity; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.ui.dialog.ConflictsResolveDialog; +import com.owncloud.android.ui.dialog.ConflictsResolveDialog.Decision; +import com.owncloud.android.ui.dialog.ConflictsResolveDialog.OnConflictDecisionMadeListener; + +import android.content.Intent; +import android.os.Bundle; + +/** + * Wrapper activity which will be launched if keep-in-sync file will be modified by external + * application. + * + * @author Bartek Przybylski + * @author David A. Velasco + */ +public class ConflictsResolveActivity extends FileActivity implements OnConflictDecisionMadeListener { + + private String TAG = ConflictsResolveActivity.class.getSimpleName(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public void ConflictDecisionMade(Decision decision) { + Intent i = new Intent(getApplicationContext(), FileUploader.class); + + switch (decision) { + case CANCEL: + finish(); + return; + case OVERWRITE: + i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true); + break; + case KEEP_BOTH: + i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE); + break; + default: + 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); + + startService(i); + finish(); + } + + @Override + protected void onAccountSet(boolean stateWasRecovered) { + if (getAccount() != null) { + OCFile file = getFile(); + if (getFile() == null) { + Log_OC.e(TAG, "No conflictive file received"); + finish(); + } else { + /// Check whether the 'main' OCFile handled by the Activity is contained in the current Account + DataStorageManager storageManager = new FileDataStorageManager(getAccount(), getContentResolver()); + file = storageManager.getFileByPath(file.getRemotePath()); // file = null if not in the current Account + if (file != null) { + setFile(file); + ConflictsResolveDialog d = ConflictsResolveDialog.newInstance(file.getRemotePath(), this); + d.showDialog(this); + + } else { + // account was changed to a different one - just finish + finish(); + } + } + + } else { + Log_OC.wtf(TAG, "onAccountChanged was called with NULL account associated!"); + finish(); + } + + } +} diff --git a/src/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java b/src/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java new file mode 100644 index 00000000..fc9afcde --- /dev/null +++ b/src/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java @@ -0,0 +1,277 @@ +/* ownCloud Android client application + * 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.activity; + +import java.io.File; +import java.util.ArrayList; + +import android.accounts.Account; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.support.v4.app.DialogFragment; +import android.text.method.ScrollingMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.ui.CustomButton; +import com.owncloud.android.ui.dialog.IndeterminateProgressDialog; +import com.owncloud.android.utils.FileStorageUtils; + + + +/** + * Activity reporting errors occurred when local files uploaded to an ownCloud account with an app in + * version under 1.3.16 where being copied to the ownCloud local folder. + * + * Allows the user move the files to the ownCloud local folder. let them unlinked to the remote + * files. + * + * Shown when the error notification summarizing the list of errors is clicked by the user. + * + * @author David A. Velasco + */ +public class ErrorsWhileCopyingHandlerActivity extends SherlockFragmentActivity implements OnClickListener { + + private static final String TAG = ErrorsWhileCopyingHandlerActivity.class.getSimpleName(); + + public static final String EXTRA_ACCOUNT = ErrorsWhileCopyingHandlerActivity.class.getCanonicalName() + ".EXTRA_ACCOUNT"; + public static final String EXTRA_LOCAL_PATHS = ErrorsWhileCopyingHandlerActivity.class.getCanonicalName() + ".EXTRA_LOCAL_PATHS"; + public static final String EXTRA_REMOTE_PATHS = ErrorsWhileCopyingHandlerActivity.class.getCanonicalName() + ".EXTRA_REMOTE_PATHS"; + + private static final String WAIT_DIALOG_TAG = "WAIT_DIALOG"; + + protected Account mAccount; + protected FileDataStorageManager mStorageManager; + protected ArrayList mLocalPaths; + protected ArrayList mRemotePaths; + protected ArrayAdapter mAdapter; + protected Handler mHandler; + private DialogFragment mCurrentDialog; + + /** + * {@link} + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + /// read extra parameters in intent + Intent intent = getIntent(); + mAccount = intent.getParcelableExtra(EXTRA_ACCOUNT); + mRemotePaths = intent.getStringArrayListExtra(EXTRA_REMOTE_PATHS); + mLocalPaths = intent.getStringArrayListExtra(EXTRA_LOCAL_PATHS); + mStorageManager = new FileDataStorageManager(mAccount, getContentResolver()); + mHandler = new Handler(); + if (mCurrentDialog != null) { + mCurrentDialog.dismiss(); + mCurrentDialog = null; + } + + /// load generic layout + setContentView(R.layout.generic_explanation); + + /// customize text message + TextView textView = (TextView) findViewById(R.id.message); + String appName = getString(R.string.app_name); + String message = String.format(getString(R.string.sync_foreign_files_forgotten_explanation), appName, appName, appName, appName, mAccount.name); + textView.setText(message); + textView.setMovementMethod(new ScrollingMovementMethod()); + + /// load the list of local and remote files that failed + ListView listView = (ListView) findViewById(R.id.list); + if (mLocalPaths != null && mLocalPaths.size() > 0) { + mAdapter = new ErrorsWhileCopyingListAdapter(); + listView.setAdapter(mAdapter); + } else { + listView.setVisibility(View.GONE); + mAdapter = null; + } + + /// customize buttons + CustomButton cancelBtn = (CustomButton) findViewById(R.id.cancel); + CustomButton okBtn = (CustomButton) findViewById(R.id.ok); + + okBtn.setText(R.string.foreign_files_move); + cancelBtn.setOnClickListener(this); + okBtn.setOnClickListener(this); + } + + + /** + * Customized adapter, showing the local files as main text in two-lines list item and the remote files + * as the secondary text. + * + * @author David A. Velasco + */ + public class ErrorsWhileCopyingListAdapter extends ArrayAdapter { + + ErrorsWhileCopyingListAdapter() { + super(ErrorsWhileCopyingHandlerActivity.this, android.R.layout.two_line_list_item, android.R.id.text1, mLocalPaths); + } + + @Override + public boolean isEnabled(int position) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public View getView (int position, View convertView, ViewGroup parent) { + View view = convertView; + if (view == null) { + LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + view = vi.inflate(android.R.layout.two_line_list_item, null); + } + if (view != null) { + String localPath = getItem(position); + if (localPath != null) { + TextView text1 = (TextView) view.findViewById(android.R.id.text1); + if (text1 != null) { + text1.setText(String.format(getString(R.string.foreign_files_local_text), localPath)); + } + } + if (mRemotePaths != null && mRemotePaths.size() > 0 && position >= 0 && position < mRemotePaths.size()) { + TextView text2 = (TextView) view.findViewById(android.R.id.text2); + String remotePath = mRemotePaths.get(position); + if (text2 != null && remotePath != null) { + text2.setText(String.format(getString(R.string.foreign_files_remote_text), remotePath)); + } + } + } + return view; + } + } + + + /** + * Listener method to perform the MOVE / CANCEL action available in this activity. + * + * @param v Clicked view (button MOVE or CANCEL) + */ + @Override + public void onClick(View v) { + if (v.getId() == R.id.ok) { + /// perform movement operation in background thread + Log_OC.d(TAG, "Clicked MOVE, start movement"); + new MoveFilesTask().execute(); + + } else if (v.getId() == R.id.cancel) { + /// just finish + Log_OC.d(TAG, "Clicked CANCEL, bye"); + finish(); + + } else { + Log_OC.e(TAG, "Clicked phantom button, id: " + v.getId()); + } + } + + + /** + * Asynchronous task performing the move of all the local files to the ownCloud folder. + * + * @author David A. Velasco + */ + private class MoveFilesTask extends AsyncTask { + + /** + * Updates the UI before trying the movement + */ + @Override + protected void onPreExecute () { + /// progress dialog and disable 'Move' button + mCurrentDialog = IndeterminateProgressDialog.newInstance(R.string.wait_a_moment, false); + mCurrentDialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG); + findViewById(R.id.ok).setEnabled(false); + } + + + /** + * Performs the movement + * + * @return 'False' when the movement of any file fails. + */ + @Override + protected Boolean doInBackground(Void... params) { + while (!mLocalPaths.isEmpty()) { + String currentPath = mLocalPaths.get(0); + File currentFile = new File(currentPath); + String expectedPath = FileStorageUtils.getSavePath(mAccount.name) + mRemotePaths.get(0); + File expectedFile = new File(expectedPath); + + if (expectedFile.equals(currentFile) || currentFile.renameTo(expectedFile)) { + // SUCCESS + OCFile file = mStorageManager.getFileByPath(mRemotePaths.get(0)); + file.setStoragePath(expectedPath); + mStorageManager.saveFile(file); + mRemotePaths.remove(0); + mLocalPaths.remove(0); + + } else { + // FAIL + return false; + } + } + return true; + } + + /** + * Updates the activity UI after the movement of local files is tried. + * + * If the movement was successful for all the files, finishes the activity immediately. + * + * In other case, the list of remaining files is still available to retry the movement. + * + * @param result 'True' when the movement was successful. + */ + @Override + protected void onPostExecute(Boolean result) { + mAdapter.notifyDataSetChanged(); + mCurrentDialog.dismiss(); + mCurrentDialog = null; + findViewById(R.id.ok).setEnabled(true); + + if (result) { + // nothing else to do in this activity + Toast t = Toast.makeText(ErrorsWhileCopyingHandlerActivity.this, getString(R.string.foreign_files_success), Toast.LENGTH_LONG); + t.show(); + finish(); + + } else { + Toast t = Toast.makeText(ErrorsWhileCopyingHandlerActivity.this, getString(R.string.foreign_files_fail), Toast.LENGTH_LONG); + t.show(); + } + } + } + +} diff --git a/src/com/owncloud/android/ui/activity/FailedUploadActivity.java b/src/com/owncloud/android/ui/activity/FailedUploadActivity.java new file mode 100644 index 00000000..76f2ba41 --- /dev/null +++ b/src/com/owncloud/android/ui/activity/FailedUploadActivity.java @@ -0,0 +1,57 @@ +/* ownCloud Android client application + * 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.activity; + +import com.owncloud.android.R; +import com.owncloud.android.ui.CustomButton; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.TextView; + + +/** + * This Activity is used to display a detail message for failed uploads + * + * The entry-point for this activity is the 'Failed upload Notification" + * + * @author andomaex / Matthias Baumann + */ +public class FailedUploadActivity extends Activity { + + public static final String MESSAGE = "message"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.failed_upload_message_view); + String message = getIntent().getStringExtra(MESSAGE); + TextView textView = (TextView) findViewById(R.id.faild_upload_message); + textView.setText(message); + CustomButton closeBtn = (CustomButton) findViewById(R.id.failed_uploadactivity_close_button); + + closeBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + } +} diff --git a/src/com/owncloud/android/ui/activity/FileActivity.java b/src/com/owncloud/android/ui/activity/FileActivity.java new file mode 100644 index 00000000..cddf9b66 --- /dev/null +++ b/src/com/owncloud/android/ui/activity/FileActivity.java @@ -0,0 +1,320 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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.activity; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.accounts.OperationCanceledException; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.webkit.MimeTypeMap; + +import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.datamodel.OCFile; + + +import eu.alefzero.webdav.WebdavUtils; + +/** + * Activity with common behaviour for activities handling {@link OCFile}s in ownCloud {@link Account}s . + * + * @author David A. Velasco + */ +public abstract class FileActivity extends SherlockFragmentActivity { + + public static final String EXTRA_FILE = "com.owncloud.android.ui.activity.FILE"; + public static final String EXTRA_ACCOUNT = "com.owncloud.android.ui.activity.ACCOUNT"; + public static final String EXTRA_WAITING_TO_PREVIEW = "com.owncloud.android.ui.activity.WAITING_TO_PREVIEW"; + + public static final String TAG = FileActivity.class.getSimpleName(); + + + /** OwnCloud {@link Account} where the main {@link OCFile} handled by the activity is located. */ + private Account mAccount; + + /** Main {@link OCFile} handled by the activity.*/ + private OCFile mFile; + + /** Flag to signal that the activity will is finishing to enforce the creation of an ownCloud {@link Account} */ + private boolean mRedirectingToSetupAccount = false; + + /** Flag to signal when the value of mAccount was set */ + private boolean mAccountWasSet; + + /** Flag to signal when the value of mAccount was restored from a saved state */ + private boolean mAccountWasRestored; + + + /** + * Loads the ownCloud {@link Account} and main {@link OCFile} to be handled by the instance of + * the {@link FileActivity}. + * + * Grants that a valid ownCloud {@link Account} is associated to the instance, or that the user + * is requested to create a new one. + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Account account; + if(savedInstanceState != null) { + account = savedInstanceState.getParcelable(FileActivity.EXTRA_ACCOUNT); + mFile = savedInstanceState.getParcelable(FileActivity.EXTRA_FILE); + } else { + account = getIntent().getParcelableExtra(FileActivity.EXTRA_ACCOUNT); + mFile = getIntent().getParcelableExtra(FileActivity.EXTRA_FILE); + } + + setAccount(account, savedInstanceState != null); + } + + + /** + * Since ownCloud {@link Account}s can be managed from the system setting menu, + * the existence of the {@link Account} associated to the instance must be checked + * every time it is restarted. + */ + @Override + protected void onRestart() { + super.onRestart(); + boolean validAccount = (mAccount != null && AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(), mAccount.name)); + if (!validAccount) { + swapToDefaultAccount(); + } + + } + + + @Override + protected void onStart() { + super.onStart(); + if (mAccountWasSet) { + onAccountSet(mAccountWasRestored); + } + } + + + /** + * Sets and validates the ownCloud {@link Account} associated to the Activity. + * + * If not valid, tries to swap it for other valid and existing ownCloud {@link Account}. + * + * POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}. + * + * @param account New {@link Account} to set. + * @param savedAccount When 'true', account was retrieved from a saved instance state. + */ + private void setAccount(Account account, boolean savedAccount) { + Account oldAccount = mAccount; + boolean validAccount = (account != null && AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(), account.name)); + if (validAccount) { + mAccount = account; + mAccountWasSet = true; + mAccountWasRestored = (savedAccount || mAccount.equals(oldAccount)); + + } else { + swapToDefaultAccount(); + } + } + + + /** + * Tries to swap the current ownCloud {@link Account} for other valid and existing. + * + * If no valid ownCloud {@link Account} exists, the the user is requested + * to create a new ownCloud {@link Account}. + * + * POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}. + * + * @return 'True' if the checked {@link Account} was valid. + */ + private void swapToDefaultAccount() { + // default to the most recently used account + Account newAccount = AccountUtils.getCurrentOwnCloudAccount(getApplicationContext()); + if (newAccount == null) { + /// no account available: force account creation + createFirstAccount(); + mRedirectingToSetupAccount = true; + mAccountWasSet = false; + mAccountWasRestored = false; + + } else { + mAccountWasSet = true; + mAccountWasRestored = (newAccount.equals(mAccount)); + mAccount = newAccount; + } + } + + + /** + * Launches the account creation activity. To use when no ownCloud account is available + */ + private void createFirstAccount() { + AccountManager am = AccountManager.get(getApplicationContext()); + am.addAccount(MainApp.getAccountType(), + null, + null, + null, + this, + new AccountCreationCallback(), + null); + } + + + /** + * {@inheritDoc} + */ + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(FileActivity.EXTRA_FILE, mFile); + outState.putParcelable(FileActivity.EXTRA_ACCOUNT, mAccount); + } + + + /** + * Getter for the main {@link OCFile} handled by the activity. + * + * @return Main {@link OCFile} handled by the activity. + */ + public OCFile getFile() { + return mFile; + } + + + /** + * Setter for the main {@link OCFile} handled by the activity. + * + * @param file Main {@link OCFile} to be handled by the activity. + */ + public void setFile(OCFile file) { + mFile = file; + } + + + /** + * Getter for the ownCloud {@link Account} where the main {@link OCFile} handled by the activity is located. + * + * @return OwnCloud {@link Account} where the main {@link OCFile} handled by the activity is located. + */ + public Account getAccount() { + return mAccount; + } + + + /** + * @return 'True' when the Activity is finishing to enforce the setup of a new account. + */ + protected boolean isRedirectingToSetupAccount() { + return mRedirectingToSetupAccount; + } + + + /** + * Helper class handling a callback from the {@link AccountManager} after the creation of + * a new ownCloud {@link Account} finished, successfully or not. + * + * At this moment, only called after the creation of the first account. + * + * @author David A. Velasco + */ + public class AccountCreationCallback implements AccountManagerCallback { + + @Override + public void run(AccountManagerFuture future) { + FileActivity.this.mRedirectingToSetupAccount = false; + boolean accountWasSet = false; + if (future != null) { + try { + Bundle result; + result = future.getResult(); + String name = result.getString(AccountManager.KEY_ACCOUNT_NAME); + String type = result.getString(AccountManager.KEY_ACCOUNT_TYPE); + if (AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(), name)) { + setAccount(new Account(name, type), false); + accountWasSet = true; + } + } catch (OperationCanceledException e) { + Log_OC.d(TAG, "Account creation canceled"); + + } catch (Exception e) { + Log_OC.e(TAG, "Account creation finished in exception: ", e); + } + + } else { + Log_OC.e(TAG, "Account creation callback with null bundle"); + } + if (!accountWasSet) { + moveTaskToBack(true); + } + } + + } + + + /** + * Called when the ownCloud {@link Account} associated to the Activity was just updated. + * + * Child classes must grant that state depending on the {@link Account} is updated. + */ + protected abstract void onAccountSet(boolean stateWasRecovered); + + + + public void openFile(OCFile file) { + if (file != null) { + String storagePath = file.getStoragePath(); + String encodedStoragePath = WebdavUtils.encodePath(storagePath); + + Intent intentForSavedMimeType = new Intent(Intent.ACTION_VIEW); + intentForSavedMimeType.setDataAndType(Uri.parse("file://"+ encodedStoragePath), file.getMimetype()); + intentForSavedMimeType.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + Intent intentForGuessedMimeType = null; + if (storagePath.lastIndexOf('.') >= 0) { + String guessedMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1)); + if (guessedMimeType != null && !guessedMimeType.equals(file.getMimetype())) { + intentForGuessedMimeType = new Intent(Intent.ACTION_VIEW); + intentForGuessedMimeType.setDataAndType(Uri.parse("file://"+ encodedStoragePath), guessedMimeType); + intentForGuessedMimeType.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } + } + + Intent chooserIntent = null; + if (intentForGuessedMimeType != null) { + chooserIntent = Intent.createChooser(intentForGuessedMimeType, getString(R.string.actionbar_open_with)); + } else { + chooserIntent = Intent.createChooser(intentForSavedMimeType, getString(R.string.actionbar_open_with)); + } + + startActivity(chooserIntent); + + } else { + Log_OC.wtf(TAG, "Trying to open a NULL OCFile"); + } + } + +} diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java new file mode 100644 index 00000000..b93321ab --- /dev/null +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -0,0 +1,1388 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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.activity; + +import java.io.File; + +import android.accounts.Account; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.app.Dialog; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.res.Resources.NotFoundException; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.provider.MediaStore; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; +import android.widget.Toast; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.ActionBar.OnNavigationListener; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.Window; +import com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.services.FileDownloader; +import com.owncloud.android.files.services.FileObserverService; +import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; +import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; +import com.owncloud.android.operations.CreateFolderOperation; +import com.owncloud.android.operations.OnRemoteOperationListener; +import com.owncloud.android.operations.RemoteOperation; +import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.operations.RemoveFileOperation; +import com.owncloud.android.operations.RenameFileOperation; +import com.owncloud.android.operations.SynchronizeFileOperation; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.syncadapter.FileSyncService; +import com.owncloud.android.ui.dialog.EditNameDialog; +import com.owncloud.android.ui.dialog.LoadingDialog; +import com.owncloud.android.ui.dialog.SslValidatorDialog; +import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener; +import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener; +import com.owncloud.android.ui.fragment.FileDetailFragment; +import com.owncloud.android.ui.fragment.FileFragment; +import com.owncloud.android.ui.fragment.OCFileListFragment; +import com.owncloud.android.ui.preview.PreviewImageActivity; +import com.owncloud.android.ui.preview.PreviewMediaFragment; +import com.owncloud.android.ui.preview.PreviewVideoActivity; + + +/** + * Displays, what files the user has available in his ownCloud. + * + * @author Bartek Przybylski + * @author David A. Velasco + */ + +public class FileDisplayActivity extends FileActivity implements +OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener, OnSslValidatorListener, OnRemoteOperationListener, EditNameDialogListener { + + private ArrayAdapter mDirectories; + + /** Access point to the cached database for the current ownCloud {@link Account} */ + private DataStorageManager mStorageManager = null; + + private SyncBroadcastReceiver mSyncBroadcastReceiver; + private UploadFinishReceiver mUploadFinishReceiver; + private DownloadFinishReceiver mDownloadFinishReceiver; + private FileDownloaderBinder mDownloaderBinder = null; + private FileUploaderBinder mUploaderBinder = null; + private ServiceConnection mDownloadConnection = null, mUploadConnection = null; + private RemoteOperationResult mLastSslUntrustedServerResult = null; + + private boolean mDualPane; + private View mLeftFragmentContainer; + private View mRightFragmentContainer; + + private static final String KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW"; + + public static final int DIALOG_SHORT_WAIT = 0; + private static final int DIALOG_CHOOSE_UPLOAD_SOURCE = 1; + private static final int DIALOG_SSL_VALIDATOR = 2; + private static final int DIALOG_CERT_NOT_SAVED = 3; + + private static final String DIALOG_WAIT_TAG = "DIALOG_WAIT"; + + public static final String ACTION_DETAILS = "com.owncloud.android.ui.activity.action.DETAILS"; + + private static final int ACTION_SELECT_CONTENT_FROM_APPS = 1; + private static final int ACTION_SELECT_MULTIPLE_FILES = 2; + + private static final String TAG = FileDisplayActivity.class.getSimpleName(); + + private static final String TAG_LIST_OF_FILES = "LIST_OF_FILES"; + private static final String TAG_SECOND_FRAGMENT = "SECOND_FRAGMENT"; + + private OCFile mWaitingToPreview; + private Handler mHandler; + + private String mDownloadAddedMessage; + private String mDownloadFinishMessage; + + @Override + protected void onCreate(Bundle savedInstanceState) { + Log_OC.d(TAG, "onCreate() start"); + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + + super.onCreate(savedInstanceState); // this calls onAccountChanged() when ownCloud Account is valid + + mHandler = new Handler(); + + FileDownloader downloader = new FileDownloader(); + mDownloadAddedMessage = downloader.getDownloadAddedMessage(); + mDownloadFinishMessage= downloader.getDownloadFinishMessage(); + + /// bindings to transference services + mUploadConnection = new ListServiceConnection(); + mDownloadConnection = new ListServiceConnection(); + bindService(new Intent(this, FileUploader.class), mUploadConnection, Context.BIND_AUTO_CREATE); + bindService(new Intent(this, FileDownloader.class), mDownloadConnection, Context.BIND_AUTO_CREATE); + + // PIN CODE request ; best location is to decide, let's try this first + if (getIntent().getAction() != null && getIntent().getAction().equals(Intent.ACTION_MAIN) && savedInstanceState == null) { + requestPinCode(); + } + + /// file observer + Intent observer_intent = new Intent(this, FileObserverService.class); + observer_intent.putExtra(FileObserverService.KEY_FILE_CMD, FileObserverService.CMD_INIT_OBSERVED_LIST); + startService(observer_intent); + + /// Load of saved instance state + if(savedInstanceState != null) { + mWaitingToPreview = (OCFile) savedInstanceState.getParcelable(FileDisplayActivity.KEY_WAITING_TO_PREVIEW); + + } else { + mWaitingToPreview = null; + } + + /// USER INTERFACE + + // Inflate and set the layout view + setContentView(R.layout.files); + mDualPane = getResources().getBoolean(R.bool.large_land_layout); + mLeftFragmentContainer = findViewById(R.id.left_fragment_container); + mRightFragmentContainer = findViewById(R.id.right_fragment_container); + if (savedInstanceState == null) { + createMinFragments(); + } + + // Action bar setup + mDirectories = new CustomArrayAdapter(this, R.layout.sherlock_spinner_dropdown_item); + getSupportActionBar().setHomeButtonEnabled(true); // mandatory since Android ICS, according to the official documentation + setSupportProgressBarIndeterminateVisibility(false); // always AFTER setContentView(...) ; to work around bug in its implementation + + + + Log_OC.d(TAG, "onCreate() end"); + } + + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mDownloadConnection != null) + unbindService(mDownloadConnection); + if (mUploadConnection != null) + unbindService(mUploadConnection); + } + + + /** + * Called when the ownCloud {@link Account} associated to the Activity was just updated. + */ + @Override + protected void onAccountSet(boolean stateWasRecovered) { + if (getAccount() != null) { + mStorageManager = new FileDataStorageManager(getAccount(), getContentResolver()); + + /// Check whether the 'main' OCFile handled by the Activity is contained in the current Account + OCFile file = getFile(); + // get parent from path + String parentPath = ""; + if (file != null) { + if (file.isDown() && file.getLastSyncDateForProperties() == 0) { + // upload in progress - right now, files are not inserted in the local cache until the upload is successful + // get parent from path + parentPath = file.getRemotePath().substring(0, file.getRemotePath().lastIndexOf(file.getFileName())); + if (mStorageManager.getFileByPath(parentPath) == null) + file = null; // not able to know the directory where the file is uploading + } else { + file = mStorageManager.getFileByPath(file.getRemotePath()); // currentDir = null if not in the current Account + } + } + if (file == null) { + // fall back to root folder + file = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR); // never returns null + } + setFile(file); + mDirectories.clear(); + OCFile fileIt = file; + while(fileIt != null && fileIt.getFileName() != OCFile.PATH_SEPARATOR) { + if (fileIt.isDirectory()) { + mDirectories.add(fileIt.getFileName()); + } + // get parent from path + parentPath = fileIt.getRemotePath().substring(0, fileIt.getRemotePath().lastIndexOf(fileIt.getFileName())); + fileIt = mStorageManager.getFileByPath(parentPath); + } + mDirectories.add(OCFile.PATH_SEPARATOR); + if (!stateWasRecovered) { + Log_OC.e(TAG, "Initializing Fragments in onAccountChanged.."); + initFragmentsWithFile(); + + } else { + updateFragmentsVisibility(!file.isDirectory()); + updateNavigationElementsInActionBar(file.isDirectory() ? null : file); + } + + + } else { + Log_OC.wtf(TAG, "onAccountChanged was called with NULL account associated!"); + } + } + + + private void createMinFragments() { + OCFileListFragment listOfFiles = new OCFileListFragment(); + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + 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(); + if (listOfFiles != null) { + listOfFiles.listDirectory(getCurrentDir()); + } else { + Log.e(TAG, "Still have a chance to lose the initializacion of list fragment >("); + } + + /// Second fragment + OCFile file = getFile(); + Fragment secondFragment = chooseInitialSecondFragment(file); + if (secondFragment != null) { + setSecondFragment(secondFragment); + updateFragmentsVisibility(true); + updateNavigationElementsInActionBar(file); + + } else { + cleanSecondFragment(); + } + + } else { + Log.wtf(TAG, "initFragments() called with invalid NULLs!"); + if (getAccount() == null) { + Log.wtf(TAG, "\t account is NULL"); + } + if (getFile() == null) { + Log.wtf(TAG, "\t file is NULL"); + } + } + } + + private Fragment chooseInitialSecondFragment(OCFile file) { + Fragment secondFragment = null; + if (file != null && !file.isDirectory()) { + if (file.isDown() && PreviewMediaFragment.canBePreviewed(file) + && file.getLastSyncDateForProperties() > 0 // temporal fix + ) { + int startPlaybackPosition = getIntent().getIntExtra(PreviewVideoActivity.EXTRA_START_POSITION, 0); + boolean autoplay = getIntent().getBooleanExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, true); + secondFragment = new PreviewMediaFragment(file, getAccount(), startPlaybackPosition, autoplay); + + } else { + secondFragment = new FileDetailFragment(file, getAccount()); + } + } + return secondFragment; + } + + + /** + * 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. + * + * @param fragment New second Fragment to set. + */ + private void setSecondFragment(Fragment fragment) { + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.right_fragment_container, fragment, TAG_SECOND_FRAGMENT); + transaction.commit(); + } + + + private void updateFragmentsVisibility(boolean existsSecondFragment) { + if (mDualPane) { + if (mLeftFragmentContainer.getVisibility() != View.VISIBLE) { + mLeftFragmentContainer.setVisibility(View.VISIBLE); + } + if (mRightFragmentContainer.getVisibility() != View.VISIBLE) { + mRightFragmentContainer.setVisibility(View.VISIBLE); + } + + } else if (existsSecondFragment) { + if (mLeftFragmentContainer.getVisibility() != View.GONE) { + mLeftFragmentContainer.setVisibility(View.GONE); + } + if (mRightFragmentContainer.getVisibility() != View.VISIBLE) { + mRightFragmentContainer.setVisibility(View.VISIBLE); + } + + } else { + if (mLeftFragmentContainer.getVisibility() != View.VISIBLE) { + mLeftFragmentContainer.setVisibility(View.VISIBLE); + } + if (mRightFragmentContainer.getVisibility() != View.GONE) { + mRightFragmentContainer.setVisibility(View.GONE); + } + } + } + + + private OCFileListFragment getListOfFilesFragment() { + Fragment listOfFiles = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES); + if (listOfFiles != null) { + return (OCFileListFragment)listOfFiles; + } + Log_OC.wtf(TAG, "Access to unexisting list of files fragment!!"); + return null; + } + + protected FileFragment getSecondFragment() { + Fragment second = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_SECOND_FRAGMENT); + if (second != null) { + return (FileFragment)second; + } + return null; + } + + public void cleanSecondFragment() { + Fragment second = getSecondFragment(); + if (second != null) { + FragmentTransaction tr = getSupportFragmentManager().beginTransaction(); + tr.remove(second); + tr.commit(); + } + updateFragmentsVisibility(false); + updateNavigationElementsInActionBar(null); + } + + protected void refeshListOfFilesFragment() { + OCFileListFragment fileListFragment = getListOfFilesFragment(); + if (fileListFragment != null) { + fileListFragment.listDirectory(); + } + } + + protected void refreshSecondFragment(String downloadEvent, String downloadedRemotePath, boolean success) { + FileFragment secondFragment = getSecondFragment(); + boolean waitedPreview = (mWaitingToPreview != null && mWaitingToPreview.getRemotePath().equals(downloadedRemotePath)); + if (secondFragment != null && secondFragment instanceof FileDetailFragment) { + FileDetailFragment detailsFragment = (FileDetailFragment) secondFragment; + OCFile fileInFragment = detailsFragment.getFile(); + if (fileInFragment != null && !downloadedRemotePath.equals(fileInFragment.getRemotePath())) { + // the user browsed to other file ; forget the automatic preview + mWaitingToPreview = null; + + } else if (downloadEvent.equals(mDownloadAddedMessage)) { + // grant that the right panel updates the progress bar + detailsFragment.listenForTransferProgress(); + detailsFragment.updateFileDetails(true, false); + + } else if (downloadEvent.equals(mDownloadFinishMessage)) { + // update the right panel + boolean detailsFragmentChanged = false; + if (waitedPreview) { + if (success) { + mWaitingToPreview = mStorageManager.getFileById(mWaitingToPreview.getFileId()); // update the file from database, for the local storage path + if (PreviewMediaFragment.canBePreviewed(mWaitingToPreview)) { + startMediaPreview(mWaitingToPreview, 0, true); + detailsFragmentChanged = true; + } else { + openFile(mWaitingToPreview); + } + } + mWaitingToPreview = null; + } + if (!detailsFragmentChanged) { + detailsFragment.updateFileDetails(false, (success)); + } + } + } + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getSherlock().getMenuInflater(); + inflater.inflate(R.menu.main_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + boolean retval = true; + switch (item.getItemId()) { + case R.id.action_create_dir: { + EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.uploader_info_dirname), "", -1, -1, this); + dialog.show(getSupportFragmentManager(), "createdirdialog"); + break; + } + case R.id.action_sync_account: { + startSynchronization(); + break; + } + case R.id.action_upload: { + showDialog(DIALOG_CHOOSE_UPLOAD_SOURCE); + break; + } + case R.id.action_settings: { + Intent settingsIntent = new Intent(this, Preferences.class); + startActivity(settingsIntent); + break; + } + case android.R.id.home: { + FileFragment second = getSecondFragment(); + OCFile currentDir = getCurrentDir(); + if((currentDir != null && currentDir.getParentId() != 0) || + (second != null && second.getFile() != null)) { + onBackPressed(); + } + break; + } + default: + retval = super.onOptionsItemSelected(item); + } + return retval; + } + + private void startSynchronization() { + ContentResolver.cancelSync(null, MainApp.getAuthTokenType()); // cancel the current synchronizations of any ownCloud account + Bundle bundle = new Bundle(); + bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + ContentResolver.requestSync( + getAccount(), + MainApp.getAuthTokenType(), bundle); + } + + + @Override + public boolean onNavigationItemSelected(int itemPosition, long itemId) { + int i = itemPosition; + while (i-- != 0) { + onBackPressed(); + } + // the next operation triggers a new call to this method, but it's necessary to + // ensure that the name exposed in the action bar is the current directory when the + // user selected it in the navigation list + if (itemPosition != 0) + getSupportActionBar().setSelectedNavigationItem(0); + return true; + } + + /** + * Called, when the user selected something for uploading + */ + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == ACTION_SELECT_CONTENT_FROM_APPS && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) { + requestSimpleUpload(data, resultCode); + + } else if (requestCode == ACTION_SELECT_MULTIPLE_FILES && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) { + requestMultipleUpload(data, resultCode); + + } + } + + private void requestMultipleUpload(Intent data, int resultCode) { + String[] filePaths = data.getStringArrayExtra(UploadFilesActivity.EXTRA_CHOSEN_FILES); + if (filePaths != null) { + String[] remotePaths = new String[filePaths.length]; + String remotePathBase = ""; + for (int j = mDirectories.getCount() - 2; j >= 0; --j) { + remotePathBase += OCFile.PATH_SEPARATOR + mDirectories.getItem(j); + } + if (!remotePathBase.endsWith(OCFile.PATH_SEPARATOR)) + remotePathBase += OCFile.PATH_SEPARATOR; + for (int j = 0; j< remotePaths.length; j++) { + 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); + if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) + i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE); + startService(i); + + } else { + Log_OC.d(TAG, "User clicked on 'Update' with no selection"); + Toast t = Toast.makeText(this, getString(R.string.filedisplay_no_file_selected), Toast.LENGTH_LONG); + t.show(); + return; + } + } + + + private void requestSimpleUpload(Intent data, int resultCode) { + String filepath = null; + try { + Uri selectedImageUri = data.getData(); + + String filemanagerstring = selectedImageUri.getPath(); + String selectedImagePath = getPath(selectedImageUri); + + if (selectedImagePath != null) + filepath = selectedImagePath; + else + filepath = filemanagerstring; + + } catch (Exception e) { + Log_OC.e(TAG, "Unexpected exception when trying to read the result of Intent.ACTION_GET_CONTENT", e); + e.printStackTrace(); + + } finally { + if (filepath == null) { + Log_OC.e(TAG, "Couldnt resolve path to file"); + Toast t = Toast.makeText(this, getString(R.string.filedisplay_unexpected_bad_get_content), Toast.LENGTH_LONG); + t.show(); + return; + } + } + + Intent i = new Intent(this, FileUploader.class); + i.putExtra(FileUploader.KEY_ACCOUNT, + getAccount()); + String remotepath = new String(); + for (int j = mDirectories.getCount() - 2; j >= 0; --j) { + remotepath += OCFile.PATH_SEPARATOR + mDirectories.getItem(j); + } + if (!remotepath.endsWith(OCFile.PATH_SEPARATOR)) + remotepath += OCFile.PATH_SEPARATOR; + remotepath += new File(filepath).getName(); + + i.putExtra(FileUploader.KEY_LOCAL_FILE, filepath); + i.putExtra(FileUploader.KEY_REMOTE_FILE, remotepath); + 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); + } + + @Override + public void onBackPressed() { + OCFileListFragment listOfFiles = getListOfFilesFragment(); + if (mDualPane || getSecondFragment() == null) { + if (listOfFiles != null) { // should never be null, indeed + if (mDirectories.getCount() <= 1) { + finish(); + return; + } + popDirname(); + listOfFiles.onBrowseUp(); + } + } + if (listOfFiles != null) { // should never be null, indeed + setFile(listOfFiles.getCurrentFile()); + } + cleanSecondFragment(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + // responsibility of restore is preferred in onCreate() before than in onRestoreInstanceState when there are Fragments involved + Log_OC.e(TAG, "onSaveInstanceState() start"); + super.onSaveInstanceState(outState); + outState.putParcelable(FileDisplayActivity.KEY_WAITING_TO_PREVIEW, mWaitingToPreview); + Log_OC.d(TAG, "onSaveInstanceState() end"); + } + + + + @Override + protected void onResume() { + super.onResume(); + Log_OC.e(TAG, "onResume() start"); + + FileUploader fileUploader = new FileUploader(); + FileSyncService fileSyncService = new FileSyncService(); + + // Listen for sync messages + IntentFilter syncIntentFilter = new IntentFilter(fileSyncService.getSyncMessage()); + mSyncBroadcastReceiver = new SyncBroadcastReceiver(); + registerReceiver(mSyncBroadcastReceiver, syncIntentFilter); + + // Listen for upload messages + IntentFilter uploadIntentFilter = new IntentFilter(fileUploader.getUploadFinishMessage()); + mUploadFinishReceiver = new UploadFinishReceiver(); + registerReceiver(mUploadFinishReceiver, uploadIntentFilter); + + // Listen for download messages + IntentFilter downloadIntentFilter = new IntentFilter(mDownloadAddedMessage); + downloadIntentFilter.addAction(mDownloadFinishMessage); + mDownloadFinishReceiver = new DownloadFinishReceiver(); + registerReceiver(mDownloadFinishReceiver, downloadIntentFilter); + + Log_OC.d(TAG, "onResume() end"); + } + + + @Override + protected void onPause() { + super.onPause(); + Log_OC.e(TAG, "onPause() start"); + if (mSyncBroadcastReceiver != null) { + unregisterReceiver(mSyncBroadcastReceiver); + mSyncBroadcastReceiver = null; + } + if (mUploadFinishReceiver != null) { + unregisterReceiver(mUploadFinishReceiver); + mUploadFinishReceiver = null; + } + if (mDownloadFinishReceiver != null) { + unregisterReceiver(mDownloadFinishReceiver); + mDownloadFinishReceiver = null; + } + + Log_OC.d(TAG, "onPause() end"); + } + + + @Override + protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { + if (id == DIALOG_SSL_VALIDATOR && mLastSslUntrustedServerResult != null) { + ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult); + } + } + + + @Override + protected Dialog onCreateDialog(int id) { + Dialog dialog = null; + AlertDialog.Builder builder; + switch (id) { + case DIALOG_SHORT_WAIT: { + ProgressDialog working_dialog = new ProgressDialog(this); + working_dialog.setMessage(getResources().getString( + R.string.wait_a_moment)); + working_dialog.setIndeterminate(true); + working_dialog.setCancelable(false); + dialog = working_dialog; + break; + } + case DIALOG_CHOOSE_UPLOAD_SOURCE: { + + String[] items = null; + + String[] allTheItems = { getString(R.string.actionbar_upload_files), + getString(R.string.actionbar_upload_from_apps), + getString(R.string.actionbar_failed_instant_upload) }; + + String[] commonItems = { getString(R.string.actionbar_upload_files), + getString(R.string.actionbar_upload_from_apps) }; + + if (InstantUploadActivity.IS_ENABLED) + items = allTheItems; + else + items = commonItems; + + builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.actionbar_upload); + builder.setItems(items, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + if (item == 0) { + // if (!mDualPane) { + Intent action = new Intent(FileDisplayActivity.this, UploadFilesActivity.class); + action.putExtra(UploadFilesActivity.EXTRA_ACCOUNT, FileDisplayActivity.this.getAccount()); + startActivityForResult(action, ACTION_SELECT_MULTIPLE_FILES); + // } else { + // TODO create and handle new fragment + // LocalFileListFragment + // } + } else if (item == 1) { + Intent action = new Intent(Intent.ACTION_GET_CONTENT); + action = action.setType("*/*").addCategory(Intent.CATEGORY_OPENABLE); + startActivityForResult(Intent.createChooser(action, getString(R.string.upload_chooser_title)), + ACTION_SELECT_CONTENT_FROM_APPS); + } else if (item == 2 && InstantUploadActivity.IS_ENABLED) { + Intent action = new Intent(FileDisplayActivity.this, InstantUploadActivity.class); + action.putExtra(FileUploader.KEY_ACCOUNT, FileDisplayActivity.this.getAccount()); + startActivity(action); + } + } + }); + dialog = builder.create(); + break; + } + case DIALOG_SSL_VALIDATOR: { + dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this); + break; + } + case DIALOG_CERT_NOT_SAVED: { + builder = new AlertDialog.Builder(this); + builder.setMessage(getResources().getString(R.string.ssl_validator_not_saved)); + builder.setCancelable(false); + builder.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + }; + }); + dialog = builder.create(); + break; + } + default: + dialog = null; + } + + return dialog; + } + + + /** + * Show loading dialog + */ + public void showLoadingDialog() { + // Construct dialog + LoadingDialog loading = new LoadingDialog(getResources().getString(R.string.wait_a_moment)); + FragmentManager fm = getSupportFragmentManager(); + FragmentTransaction ft = fm.beginTransaction(); + loading.show(ft, DIALOG_WAIT_TAG); + + } + + /** + * Dismiss loading dialog + */ + public void dismissLoadingDialog(){ + Fragment frag = getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG); + if (frag != null) { + LoadingDialog loading = (LoadingDialog) frag; + loading.dismiss(); + } + } + + + /** + * Translates a content URI of an image to a physical path + * on the disk + * @param uri The URI to resolve + * @return The path to the image or null if it could not be found + */ + public String getPath(Uri uri) { + String[] projection = { MediaStore.Images.Media.DATA }; + Cursor cursor = managedQuery(uri, projection, null, null, null); + if (cursor != null) { + int column_index = cursor + .getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + cursor.moveToFirst(); + return cursor.getString(column_index); + } + return null; + } + + /** + * Pushes a directory to the drop down list + * @param directory to push + * @throws IllegalArgumentException If the {@link OCFile#isDirectory()} returns false. + */ + public void pushDirname(OCFile directory) { + if(!directory.isDirectory()){ + throw new IllegalArgumentException("Only directories may be pushed!"); + } + mDirectories.insert(directory.getFileName(), 0); + setFile(directory); + } + + /** + * Pops a directory name from the drop down list + * @return True, unless the stack is empty + */ + public boolean popDirname() { + mDirectories.remove(mDirectories.getItem(0)); + return !mDirectories.isEmpty(); + } + + // Custom array adapter to override text colors + private class CustomArrayAdapter extends ArrayAdapter { + + public CustomArrayAdapter(FileDisplayActivity ctx, int view) { + super(ctx, view); + } + + public View getView(int position, View convertView, ViewGroup parent) { + View v = super.getView(position, convertView, parent); + + ((TextView) v).setTextColor(getResources().getColorStateList( + android.R.color.white)); + return v; + } + + public View getDropDownView(int position, View convertView, + ViewGroup parent) { + View v = super.getDropDownView(position, convertView, parent); + + ((TextView) v).setTextColor(getResources().getColorStateList( + android.R.color.white)); + + return v; + } + + } + + private class SyncBroadcastReceiver extends BroadcastReceiver { + + /** + * {@link BroadcastReceiver} to enable syncing feedback in UI + */ + @Override + public void onReceive(Context context, Intent intent) { + boolean inProgress = intent.getBooleanExtra(FileSyncService.IN_PROGRESS, false); + String accountName = intent.getStringExtra(FileSyncService.ACCOUNT_NAME); + + Log_OC.d(TAG, "sync of account " + accountName + " is in_progress: " + inProgress); + + if (getAccount() != null && accountName.equals(getAccount().name) + && mStorageManager != null + ) { + + String synchFolderRemotePath = intent.getStringExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH); + + boolean fillBlankRoot = false; + OCFile currentDir = getCurrentDir(); + if (currentDir == null) { + currentDir = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR); + fillBlankRoot = (currentDir != null); + } + + if ((synchFolderRemotePath != null && currentDir != null && (currentDir.getRemotePath().equals(synchFolderRemotePath))) + || fillBlankRoot ) { + if (!fillBlankRoot) + currentDir = mStorageManager.getFileByPath(synchFolderRemotePath); + OCFileListFragment fileListFragment = getListOfFilesFragment(); + if (fileListFragment != null) { + fileListFragment.listDirectory(currentDir); + } + if (getSecondFragment() == null) + setFile(currentDir); + } + + setSupportProgressBarIndeterminateVisibility(inProgress); + removeStickyBroadcast(intent); + + } + + RemoteOperationResult synchResult = (RemoteOperationResult)intent.getSerializableExtra(FileSyncService.SYNC_RESULT); + if (synchResult != null) { + if (synchResult.getCode().equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED)) { + mLastSslUntrustedServerResult = synchResult; + showDialog(DIALOG_SSL_VALIDATOR); + } + } + } + } + + + private class UploadFinishReceiver extends BroadcastReceiver { + /** + * Once the file upload has finished -> update view + * @author David A. Velasco + * {@link BroadcastReceiver} to enable upload feedback in UI + */ + @Override + public void onReceive(Context context, Intent intent) { + String uploadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); + String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME); + boolean sameAccount = getAccount() != null && accountName.equals(getAccount().name); + OCFile currentDir = getCurrentDir(); + boolean isDescendant = (currentDir != null) && (uploadedRemotePath != null) && (uploadedRemotePath.startsWith(currentDir.getRemotePath())); + if (sameAccount && isDescendant) { + refeshListOfFilesFragment(); + } + } + + } + + + /** + * Class waiting for broadcast events from the {@link FielDownloader} service. + * + * Updates the UI when a download is started or finished, provided that it is relevant for the + * current folder. + */ + private class DownloadFinishReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + boolean sameAccount = isSameAccount(context, intent); + String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); + boolean isDescendant = isDescendant(downloadedRemotePath); + + if (sameAccount && isDescendant) { + refeshListOfFilesFragment(); + refreshSecondFragment(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false)); + } + + removeStickyBroadcast(intent); + } + + private boolean isDescendant(String downloadedRemotePath) { + OCFile currentDir = getCurrentDir(); + return (currentDir != null && downloadedRemotePath != null && downloadedRemotePath.startsWith(currentDir.getRemotePath())); + } + + private boolean isSameAccount(Context context, Intent intent) { + String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME); + return (accountName != null && getAccount() != null && accountName.equals(getAccount().name)); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public DataStorageManager getStorageManager() { + return mStorageManager; + } + + + /** + * {@inheritDoc} + * + * Updates action bar and second fragment, if in dual pane mode. + */ + @Override + public void onBrowsedDownTo(OCFile directory) { + pushDirname(directory); + cleanSecondFragment(); + } + + /** + * Opens the image gallery showing the image {@link OCFile} received as parameter. + * + * @param file Image {@link OCFile} to show. + */ + @Override + public void startImagePreview(OCFile file) { + Intent showDetailsIntent = new Intent(this, PreviewImageActivity.class); + showDetailsIntent.putExtra(EXTRA_FILE, 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. + * @param autoplay When 'true', the playback will start without user interactions. + */ + @Override + public void startMediaPreview(OCFile file, int startPlaybackPosition, boolean autoplay) { + Fragment mediaFragment = new PreviewMediaFragment(file, getAccount(), startPlaybackPosition, autoplay); + setSecondFragment(mediaFragment); + updateFragmentsVisibility(true); + updateNavigationElementsInActionBar(file); + setFile(file); + } + + /** + * 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. + */ + @Override + public void startDownloadForPreview(OCFile file) { + Fragment detailFragment = new FileDetailFragment(file, getAccount()); + setSecondFragment(detailFragment); + mWaitingToPreview = file; + requestForDownload(); + updateFragmentsVisibility(true); + updateNavigationElementsInActionBar(file); + setFile(file); + } + + + /** + * 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 + public void showDetails(OCFile file) { + Fragment detailFragment = new FileDetailFragment(file, getAccount()); + setSecondFragment(detailFragment); + updateFragmentsVisibility(true); + updateNavigationElementsInActionBar(file); + setFile(file); + } + + + /** + * TODO + */ + private void updateNavigationElementsInActionBar(OCFile chosenFile) { + ActionBar actionBar = getSupportActionBar(); + if (chosenFile == null || mDualPane) { + // only list of files - set for browsing through folders + OCFile currentDir = getCurrentDir(); + actionBar.setDisplayHomeAsUpEnabled(currentDir != null && currentDir.getParentId() != 0); + actionBar.setDisplayShowTitleEnabled(false); + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); + actionBar.setListNavigationCallbacks(mDirectories, this); // assuming mDirectories is updated + + } else { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setTitle(chosenFile.getFileName()); + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public void onFileStateChanged() { + refeshListOfFilesFragment(); + updateNavigationElementsInActionBar(getSecondFragment().getFile()); + } + + + /** + * {@inheritDoc} + */ + @Override + public FileDownloaderBinder getFileDownloaderBinder() { + return mDownloaderBinder; + } + + + /** + * {@inheritDoc} + */ + @Override + public FileUploaderBinder getFileUploaderBinder() { + return mUploaderBinder; + } + + + /** Defines callbacks for service binding, passed to bindService() */ + private class ListServiceConnection implements ServiceConnection { + + @Override + public void onServiceConnected(ComponentName component, IBinder service) { + if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloader.class))) { + Log_OC.d(TAG, "Download service connected"); + mDownloaderBinder = (FileDownloaderBinder) service; + if (mWaitingToPreview != null) { + requestForDownload(); + } + + } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) { + Log_OC.d(TAG, "Upload service connected"); + mUploaderBinder = (FileUploaderBinder) service; + } else { + return; + } + // a new chance to get the mDownloadBinder through getFileDownloadBinder() - THIS IS A MESS + OCFileListFragment listOfFiles = getListOfFilesFragment(); + if (listOfFiles != null) { + listOfFiles.listDirectory(); + } + FileFragment secondFragment = getSecondFragment(); + if (secondFragment != null && secondFragment instanceof FileDetailFragment) { + FileDetailFragment detailFragment = (FileDetailFragment)secondFragment; + detailFragment.listenForTransferProgress(); + detailFragment.updateFileDetails(false, false); + } + } + + @Override + public void onServiceDisconnected(ComponentName component) { + if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloader.class))) { + Log_OC.d(TAG, "Download service disconnected"); + mDownloaderBinder = null; + } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) { + Log_OC.d(TAG, "Upload service disconnected"); + mUploaderBinder = null; + } + } + }; + + + + /** + * Launch an intent to request the PIN code to the user before letting him use the app + */ + private void requestPinCode() { + boolean pinStart = false; + SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + pinStart = appPrefs.getBoolean("set_pincode", false); + if (pinStart) { + Intent i = new Intent(getApplicationContext(), PinCodeActivity.class); + i.putExtra(PinCodeActivity.EXTRA_ACTIVITY, "FileDisplayActivity"); + startActivity(i); + } + } + + + @Override + public void onSavedCertificate() { + startSynchronization(); + } + + + @Override + public void onFailedSavingCertificate() { + showDialog(DIALOG_CERT_NOT_SAVED); + } + + + /** + * 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) { + if (operation instanceof RemoveFileOperation) { + onRemoveFileOperationFinish((RemoveFileOperation)operation, result); + + } else if (operation instanceof RenameFileOperation) { + onRenameFileOperationFinish((RenameFileOperation)operation, result); + + } else if (operation instanceof SynchronizeFileOperation) { + onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result); + + } else if (operation instanceof CreateFolderOperation) { + onCreateFolderOperationFinish((CreateFolderOperation)operation, result); + } + } + + + /** + * 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. + */ + private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) { + dismissLoadingDialog(); + if (result.isSuccess()) { + Toast msg = Toast.makeText(this, R.string.remove_success_msg, Toast.LENGTH_LONG); + msg.show(); + OCFile removedFile = operation.getFile(); + getSecondFragment(); + FileFragment second = getSecondFragment(); + if (second != null && removedFile.equals(second.getFile())) { + cleanSecondFragment(); + } + if (mStorageManager.getFileById(removedFile.getParentId()).equals(getCurrentDir())) { + refeshListOfFilesFragment(); + } + + } else { + Toast msg = Toast.makeText(this, R.string.remove_fail_msg, Toast.LENGTH_LONG); + msg.show(); + if (result.isSslRecoverableException()) { + mLastSslUntrustedServerResult = result; + showDialog(DIALOG_SSL_VALIDATOR); + } + } + } + + /** + * 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. + */ + private void onCreateFolderOperationFinish(CreateFolderOperation operation, RemoteOperationResult result) { + if (result.isSuccess()) { + dismissLoadingDialog(); + refeshListOfFilesFragment(); + + } else { + //dismissDialog(DIALOG_SHORT_WAIT); + dismissLoadingDialog(); + try { + Toast msg = Toast.makeText(FileDisplayActivity.this, R.string.create_dir_fail_msg, Toast.LENGTH_LONG); + msg.show(); + + } catch (NotFoundException e) { + Log_OC.e(TAG, "Error while trying to show fail message " , e); + } + } + } + + + /** + * 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. + */ + private void onRenameFileOperationFinish(RenameFileOperation operation, RemoteOperationResult result) { + dismissLoadingDialog(); + OCFile renamedFile = operation.getFile(); + if (result.isSuccess()) { + if (mDualPane) { + FileFragment details = getSecondFragment(); + if (details != null && details instanceof FileDetailFragment && renamedFile.equals(details.getFile()) ) { + ((FileDetailFragment) details).updateFileDetails(renamedFile, getAccount()); + } + } + if (mStorageManager.getFileById(renamedFile.getParentId()).equals(getCurrentDir())) { + refeshListOfFilesFragment(); + } + + } else { + if (result.getCode().equals(ResultCode.INVALID_LOCAL_FILE_NAME)) { + Toast msg = Toast.makeText(this, R.string.rename_local_fail_msg, Toast.LENGTH_LONG); + msg.show(); + // TODO throw again the new rename dialog + } else { + Toast msg = Toast.makeText(this, R.string.rename_server_fail_msg, Toast.LENGTH_LONG); + msg.show(); + if (result.isSslRecoverableException()) { + mLastSslUntrustedServerResult = result; + showDialog(DIALOG_SSL_VALIDATOR); + } + } + } + } + + + private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) { + dismissLoadingDialog(); + OCFile syncedFile = operation.getLocalFile(); + 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_ACCOUNT, getAccount()); + startActivity(i); + + } else { + Toast msg = Toast.makeText(this, R.string.sync_file_fail_msg, Toast.LENGTH_LONG); + msg.show(); + } + + } else { + if (operation.transferWasRequested()) { + refeshListOfFilesFragment(); + onTransferStateChanged(syncedFile, true, true); + + } else { + Toast msg = Toast.makeText(this, R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG); + msg.show(); + } + } + } + + + /** + * {@inheritDoc} + */ + @Override + public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading) { + if (mDualPane) { + FileFragment details = getSecondFragment(); + if (details != null && details instanceof FileDetailFragment && file.equals(details.getFile()) ) { + if (downloading || uploading) { + ((FileDetailFragment)details).updateFileDetails(file, getAccount()); + } else { + ((FileDetailFragment)details).updateFileDetails(false, true); + } + } + } + } + + + public void onDismiss(EditNameDialog dialog) { + if (dialog.getResult()) { + String newDirectoryName = dialog.getNewFilename().trim(); + Log_OC.d(TAG, "'create directory' dialog dismissed with new name " + newDirectoryName); + if (newDirectoryName.length() > 0) { + String path = getCurrentDir().getRemotePath(); + + // Create directory + path += newDirectoryName + OCFile.PATH_SEPARATOR; + RemoteOperation operation = new CreateFolderOperation(path, false, mStorageManager); + operation.execute( getAccount(), + FileDisplayActivity.this, + FileDisplayActivity.this, + mHandler, + FileDisplayActivity.this); + + showLoadingDialog(); + } + } + } + + + private void requestForDownload() { + 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, mWaitingToPreview); + startService(i); + } + } + + + private OCFile getCurrentDir() { + OCFile file = getFile(); + if (file != null) { + if (file.isDirectory()) { + return file; + } else if (mStorageManager != null) { + String parentPath = file.getRemotePath().substring(0, file.getRemotePath().lastIndexOf(file.getFileName())); + return mStorageManager.getFileByPath(parentPath); + } + } + return null; + } + +} diff --git a/src/com/owncloud/android/ui/activity/GenericExplanationActivity.java b/src/com/owncloud/android/ui/activity/GenericExplanationActivity.java new file mode 100644 index 00000000..b971c40c --- /dev/null +++ b/src/com/owncloud/android/ui/activity/GenericExplanationActivity.java @@ -0,0 +1,113 @@ +/* ownCloud Android client application + * 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.activity; + +import java.util.ArrayList; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.text.method.ScrollingMovementMethod; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.owncloud.android.R; + + +/** + * Activity showing a text message and, optionally, a couple list of single or paired text strings. + * + * Added to show explanations for notifications when the user clicks on them, and there no place + * better to show them. + * + * @author David A. Velasco + */ +public class GenericExplanationActivity extends SherlockFragmentActivity { + + public static final String EXTRA_LIST = GenericExplanationActivity.class.getCanonicalName() + ".EXTRA_LIST"; + public static final String EXTRA_LIST_2 = GenericExplanationActivity.class.getCanonicalName() + ".EXTRA_LIST_2"; + public static final String MESSAGE = GenericExplanationActivity.class.getCanonicalName() + ".MESSAGE"; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + String message = intent.getStringExtra(MESSAGE); + ArrayList list = intent.getStringArrayListExtra(EXTRA_LIST); + ArrayList list2 = intent.getStringArrayListExtra(EXTRA_LIST_2); + + setContentView(R.layout.generic_explanation); + + if (message != null) { + TextView textView = (TextView) findViewById(R.id.message); + textView.setText(message); + textView.setMovementMethod(new ScrollingMovementMethod()); + } + + ListView listView = (ListView) findViewById(R.id.list); + if (list != null && list.size() > 0) { + //ListAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, list); + ListAdapter adapter = new ExplanationListAdapterView(this, list, list2); + listView.setAdapter(adapter); + } else { + listView.setVisibility(View.GONE); + } + } + + public class ExplanationListAdapterView extends ArrayAdapter { + + ArrayList mList; + ArrayList mList2; + + ExplanationListAdapterView(Context context, ArrayList list, ArrayList list2) { + super(context, android.R.layout.two_line_list_item, android.R.id.text1, list); + mList = list; + mList2 = list2; + } + + @Override + public boolean isEnabled(int position) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public View getView (int position, View convertView, ViewGroup parent) { + View view = super.getView(position, convertView, parent); + if (view != null) { + if (mList2 != null && mList2.size() > 0 && position >= 0 && position < mList2.size()) { + TextView text2 = (TextView) view.findViewById(android.R.id.text2); + if (text2 != null) { + text2.setText(mList2.get(position)); + } + } + } + return view; + } + } + +} diff --git a/src/com/owncloud/android/ui/activity/InstantUploadActivity.java b/src/com/owncloud/android/ui/activity/InstantUploadActivity.java new file mode 100644 index 00000000..ce7ef798 --- /dev/null +++ b/src/com/owncloud/android/ui/activity/InstantUploadActivity.java @@ -0,0 +1,477 @@ +/* ownCloud Android client application + * 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.activity; + +import java.util.ArrayList; +import java.util.List; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.db.DbHandler; +import com.owncloud.android.files.InstantUploadBroadcastReceiver; +import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.ui.CustomButton; +import com.owncloud.android.utils.FileStorageUtils; + +import android.accounts.Account; +import android.app.Activity; +import android.content.Intent; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.util.SparseArray; +import android.view.Gravity; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + + +/** + * This Activity is used to display a list with images they could not be + * uploaded instantly. The images can be selected for delete or for a try again + * upload + * + * The entry-point for this activity is the 'Failed upload Notification" and a + * sub-menu underneath the 'Upload' menu-item + * + * @author andomaex / Matthias Baumann + */ +public class InstantUploadActivity extends Activity { + + private static final String LOG_TAG = InstantUploadActivity.class.getSimpleName(); + private LinearLayout listView; + private static final String retry_chexbox_tag = "retry_chexbox_tag"; + public static final boolean IS_ENABLED = false; + private static int MAX_LOAD_IMAGES = 5; + private int lastLoadImageIdx = 0; + + private SparseArray fileList = null; + CheckBox failed_upload_all_cb; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.failed_upload_files); + + CustomButton deleteAllBtn = (CustomButton) findViewById(R.id.failed_upload_delete_all_btn); + deleteAllBtn.setOnClickListener(getDeleteListner()); + CustomButton retryAllBtn = (CustomButton) findViewById(R.id.failed_upload_retry_all_btn); + retryAllBtn.setOnClickListener(getRetryListner()); + this.failed_upload_all_cb = (CheckBox) findViewById(R.id.failed_upload_headline_cb); + failed_upload_all_cb.setOnCheckedChangeListener(getCheckAllListener()); + listView = (LinearLayout) findViewById(R.id.failed_upload_scrollviewlayout); + + loadListView(true); + + } + + /** + * init the listview with ImageButtons, checkboxes and filename for every + * Image that was not successfully uploaded + * + * this method is call at Activity creation and on delete one ore more + * list-entry an on retry the upload by clicking the ImageButton or by click + * to the 'retry all' button + * + */ + private void loadListView(boolean reset) { + DbHandler db = new DbHandler(getApplicationContext()); + Cursor c = db.getFailedFiles(); + + if (reset) { + fileList = new SparseArray(); + listView.removeAllViews(); + lastLoadImageIdx = 0; + } + if (c != null) { + try { + c.moveToPosition(lastLoadImageIdx); + + while (c.moveToNext()) { + + lastLoadImageIdx++; + String imp_path = c.getString(1); + String message = c.getString(4); + fileList.put(lastLoadImageIdx, imp_path); + LinearLayout rowLayout = getHorizontalLinearLayout(lastLoadImageIdx); + rowLayout.addView(getFileCheckbox(lastLoadImageIdx)); + rowLayout.addView(getImageButton(imp_path, lastLoadImageIdx)); + rowLayout.addView(getFileButton(imp_path, message, lastLoadImageIdx)); + listView.addView(rowLayout); + Log_OC.d(LOG_TAG, imp_path + " on idx: " + lastLoadImageIdx); + if (lastLoadImageIdx % MAX_LOAD_IMAGES == 0) { + break; + } + } + if (lastLoadImageIdx > 0) { + addLoadMoreButton(listView); + } + } finally { + db.close(); + } + } + } + + private void addLoadMoreButton(LinearLayout listView) { + if (listView != null) { + Button loadmoreBtn = null; + View oldButton = listView.findViewById(42); + if (oldButton != null) { + // remove existing button + listView.removeView(oldButton); + // to add the button at the end + loadmoreBtn = (Button) oldButton; + } else { + // create a new button to add to the scoll view + loadmoreBtn = new Button(this); + loadmoreBtn.setId(42); + loadmoreBtn.setText(getString(R.string.failed_upload_load_more_images)); + loadmoreBtn.setBackgroundResource(R.color.background_color); + loadmoreBtn.setTextSize(12); + loadmoreBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + loadListView(false); + } + + }); + } + listView.addView(loadmoreBtn); + } + } + + /** + * provide a list of CheckBox instances, looked up from parent listview this + * list ist used to select/deselect all checkboxes at the list + * + * @return List + */ + private List getCheckboxList() { + List list = new ArrayList(); + for (int i = 0; i < listView.getChildCount(); i++) { + Log_OC.d(LOG_TAG, "ListView has Childs: " + listView.getChildCount()); + View childView = listView.getChildAt(i); + if (childView != null && childView instanceof ViewGroup) { + View checkboxView = getChildViews((ViewGroup) childView); + if (checkboxView != null && checkboxView instanceof CheckBox) { + Log_OC.d(LOG_TAG, "found Child: " + checkboxView.getId() + " " + checkboxView.getClass()); + list.add((CheckBox) checkboxView); + } + } + } + return list; + } + + /** + * recursive called method, used from getCheckboxList method + * + * @param View + * @return View + */ + private View getChildViews(ViewGroup view) { + if (view != null) { + for (int i = 0; i < view.getChildCount(); i++) { + View cb = view.getChildAt(i); + if (cb != null && cb instanceof ViewGroup) { + return getChildViews((ViewGroup) cb); + } else if (cb instanceof CheckBox) { + return cb; + } + } + } + return null; + } + + /** + * create a new OnCheckedChangeListener for the 'check all' checkbox * + * + * @return OnCheckedChangeListener to select all checkboxes at the list + */ + private OnCheckedChangeListener getCheckAllListener() { + return new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + List list = getCheckboxList(); + for (CheckBox checkbox : list) { + ((CheckBox) checkbox).setChecked(isChecked); + } + } + + }; + } + + /** + * Button click Listener for the retry button at the headline + * + * @return a Listener to perform a retry for all selected images + */ + private OnClickListener getRetryListner() { + return new OnClickListener() { + + @Override + public void onClick(View v) { + + try { + + List list = getCheckboxList(); + for (CheckBox checkbox : list) { + boolean to_retry = checkbox.isChecked(); + + Log_OC.d(LOG_TAG, "Checkbox for " + checkbox.getId() + " was checked: " + to_retry); + String img_path = fileList.get(checkbox.getId()); + if (to_retry) { + + final String msg = "Image-Path " + checkbox.getId() + " was checked: " + img_path; + Log_OC.d(LOG_TAG, msg); + startUpload(img_path); + } + + } + } finally { + // refresh the List + listView.removeAllViews(); + loadListView(true); + if (failed_upload_all_cb != null) { + failed_upload_all_cb.setChecked(false); + } + } + + } + }; + } + + /** + * Button click Listener for the delete button at the headline + * + * @return a Listener to perform a delete for all selected images + */ + private OnClickListener getDeleteListner() { + + return new OnClickListener() { + + @Override + public void onClick(View v) { + + final DbHandler dbh = new DbHandler(getApplicationContext()); + try { + List list = getCheckboxList(); + for (CheckBox checkbox : list) { + boolean to_be_delete = checkbox.isChecked(); + + Log_OC.d(LOG_TAG, "Checkbox for " + checkbox.getId() + " was checked: " + to_be_delete); + String img_path = fileList.get(checkbox.getId()); + Log_OC.d(LOG_TAG, "Image-Path " + checkbox.getId() + " was checked: " + img_path); + if (to_be_delete) { + boolean deleted = dbh.removeIUPendingFile(img_path); + Log_OC.d(LOG_TAG, "removing " + checkbox.getId() + " was : " + deleted); + + } + + } + } finally { + dbh.close(); + // refresh the List + listView.removeAllViews(); + loadListView(true); + if (failed_upload_all_cb != null) { + failed_upload_all_cb.setChecked(false); + } + } + + } + }; + } + + private LinearLayout getHorizontalLinearLayout(int id) { + LinearLayout linearLayout = new LinearLayout(getApplicationContext()); + linearLayout.setId(id); + linearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.MATCH_PARENT)); + linearLayout.setGravity(Gravity.RIGHT); + linearLayout.setOrientation(LinearLayout.HORIZONTAL); + return linearLayout; + } + + private LinearLayout getVerticalLinearLayout() { + LinearLayout linearLayout = new LinearLayout(getApplicationContext()); + linearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.MATCH_PARENT)); + linearLayout.setGravity(Gravity.TOP); + linearLayout.setOrientation(LinearLayout.VERTICAL); + return linearLayout; + } + + private View getFileButton(final String img_path, String message, int id) { + + TextView failureTextView = new TextView(this); + failureTextView.setText(getString(R.string.failed_upload_failure_text) + message); + failureTextView.setBackgroundResource(R.color.background_color); + failureTextView.setTextSize(8); + failureTextView.setOnLongClickListener(getOnLongClickListener(message)); + failureTextView.setPadding(5, 5, 5, 10); + TextView retryButton = new TextView(this); + retryButton.setId(id); + retryButton.setText(img_path); + retryButton.setBackgroundResource(R.color.background_color); + retryButton.setTextSize(8); + retryButton.setOnClickListener(getImageButtonOnClickListener(img_path)); + retryButton.setOnLongClickListener(getOnLongClickListener(message)); + retryButton.setPadding(5, 5, 5, 10); + LinearLayout verticalLayout = getVerticalLinearLayout(); + verticalLayout.addView(retryButton); + verticalLayout.addView(failureTextView); + + return verticalLayout; + } + + private OnLongClickListener getOnLongClickListener(final String message) { + return new OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + Log_OC.d(LOG_TAG, message); + Toast toast = Toast.makeText(InstantUploadActivity.this, getString(R.string.failed_upload_retry_text) + + message, Toast.LENGTH_LONG); + toast.show(); + return true; + } + + }; + } + + private CheckBox getFileCheckbox(int id) { + CheckBox retryCB = new CheckBox(this); + retryCB.setId(id); + retryCB.setBackgroundResource(R.color.background_color); + retryCB.setTextSize(8); + retryCB.setTag(retry_chexbox_tag); + return retryCB; + } + + private ImageButton getImageButton(String img_path, int id) { + ImageButton imageButton = new ImageButton(this); + imageButton.setId(id); + imageButton.setClickable(true); + imageButton.setOnClickListener(getImageButtonOnClickListener(img_path)); + + // scale and add a thumbnail to the imagebutton + int base_scale_size = 32; + if (img_path != null) { + Log_OC.d(LOG_TAG, "add " + img_path + " to Image Button"); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + Bitmap bitmap = BitmapFactory.decodeFile(img_path, options); + int width_tpm = options.outWidth, height_tmp = options.outHeight; + int scale = 3; + while (true) { + if (width_tpm / 2 < base_scale_size || height_tmp / 2 < base_scale_size) { + break; + } + width_tpm /= 2; + height_tmp /= 2; + scale++; + } + + Log_OC.d(LOG_TAG, "scale Imgae with: " + scale); + BitmapFactory.Options options2 = new BitmapFactory.Options(); + options2.inSampleSize = scale; + bitmap = BitmapFactory.decodeFile(img_path, options2); + + if (bitmap != null) { + Log_OC.d(LOG_TAG, "loaded Bitmap Bytes: " + bitmap.getRowBytes()); + imageButton.setImageBitmap(bitmap); + } else { + Log_OC.d(LOG_TAG, "could not load imgage: " + img_path); + } + } + return imageButton; + } + + private OnClickListener getImageButtonOnClickListener(final String img_path) { + return new OnClickListener() { + + @Override + public void onClick(View v) { + startUpload(img_path); + loadListView(true); + } + + }; + } + + /** + * start uploading a file to the INSTANT_UPLOD_DIR + * + * @param img_path + */ + private void startUpload(String img_path) { + // extract filename + String filename = FileStorageUtils.getInstantUploadFilePath(this, img_path); + if (canInstantUpload()) { + Account account = AccountUtils.getCurrentOwnCloudAccount(InstantUploadActivity.this); + // add file again to upload queue + DbHandler db = new DbHandler(InstantUploadActivity.this); + try { + db.updateFileState(img_path, DbHandler.UPLOAD_STATUS_UPLOAD_LATER, null); + } finally { + db.close(); + } + + Intent i = new Intent(InstantUploadActivity.this, FileUploader.class); + i.putExtra(FileUploader.KEY_ACCOUNT, account); + i.putExtra(FileUploader.KEY_LOCAL_FILE, img_path); + i.putExtra(FileUploader.KEY_REMOTE_FILE, filename); + i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE); + i.putExtra(com.owncloud.android.files.services.FileUploader.KEY_INSTANT_UPLOAD, true); + + final String msg = "try to upload file with name :" + filename; + Log_OC.d(LOG_TAG, msg); + Toast toast = Toast.makeText(InstantUploadActivity.this, getString(R.string.failed_upload_retry_text) + + filename, Toast.LENGTH_LONG); + toast.show(); + + startService(i); + } else { + Toast toast = Toast.makeText(InstantUploadActivity.this, + getString(R.string.failed_upload_retry_do_nothing_text) + filename, Toast.LENGTH_LONG); + toast.show(); + } + } + + private boolean canInstantUpload() { + + if (!InstantUploadBroadcastReceiver.isOnline(this) + || (InstantUploadBroadcastReceiver.instantUploadViaWiFiOnly(this) && !InstantUploadBroadcastReceiver + .isConnectedViaWiFi(this))) { + return false; + } else { + return true; + } + } + +} \ No newline at end of file diff --git a/src/com/owncloud/android/ui/activity/LandingActivity.java b/src/com/owncloud/android/ui/activity/LandingActivity.java new file mode 100644 index 00000000..a6ada6ce --- /dev/null +++ b/src/com/owncloud/android/ui/activity/LandingActivity.java @@ -0,0 +1,158 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.owncloud.android.ui.activity; + +import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.owncloud.android.MainApp; +import com.owncloud.android.R; +import com.owncloud.android.ui.adapter.LandingScreenAdapter; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.GridView; +import android.widget.Toast; + + +/** + * This activity is used as a landing page when the user first opens this app. + * + * @author Lennart Rosam + * + */ +public class LandingActivity extends SherlockFragmentActivity implements + OnClickListener, OnItemClickListener { + + public static final int DIALOG_SETUP_ACCOUNT = 1; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + // Fill the grid view of the landing screen with icons + GridView landingScreenItems = (GridView) findViewById(R.id.homeScreenGrid); + landingScreenItems.setAdapter(new LandingScreenAdapter(this)); + landingScreenItems.setOnItemClickListener(this); + + // Check, if there are ownCloud accounts + if (!accountsAreSetup()) { + showDialog(DIALOG_SETUP_ACCOUNT); + } else { + // Start device tracking service + Intent locationServiceIntent = new Intent(); + locationServiceIntent + .setAction("com.owncloud.android.location.LocationLauncher"); + sendBroadcast(locationServiceIntent); + } + + } + + @Override + protected void onRestart() { + super.onRestart(); + // Check, if there are ownCloud accounts + if (!accountsAreSetup()) { + showDialog(DIALOG_SETUP_ACCOUNT); + } + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + // Check, if there are ownCloud accounts + if (!accountsAreSetup()) { + showDialog(DIALOG_SETUP_ACCOUNT); + } + } + + @Override + protected Dialog onCreateDialog(int id) { + Dialog dialog; + switch (id) { + case DIALOG_SETUP_ACCOUNT: + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.main_tit_accsetup); + builder.setMessage(R.string.main_wrn_accsetup); + builder.setCancelable(false); + builder.setPositiveButton(R.string.common_ok, this); + builder.setNegativeButton(R.string.common_cancel, this); + dialog = builder.create(); + break; + default: + dialog = null; + } + + return dialog; + } + + public void onClick(DialogInterface dialog, int which) { + // In any case - we won't need it anymore + dialog.dismiss(); + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT); + intent.putExtra("authorities", + new String[] { MainApp.getAuthTokenType() }); + startActivity(intent); + break; + case DialogInterface.BUTTON_NEGATIVE: + finish(); + } + + } + + @Override + /** + * Start an activity based on the selection + * the user made + */ + public void onItemClick(AdapterView parent, View view, int position, + long id) { + Intent intent; + intent = (Intent) parent.getAdapter().getItem(position); + if (intent != null) { + startActivity(intent); + } else { + // TODO: Implement all of this and make this text go away ;-) + Toast toast = Toast.makeText(this, "Not yet implemented!", + Toast.LENGTH_SHORT); + toast.show(); + } + } + + /** + * Checks, whether or not there are any ownCloud accounts setup. + * + * @return true, if there is at least one account. + */ + private boolean accountsAreSetup() { + AccountManager accMan = AccountManager.get(this); + Account[] accounts = accMan + .getAccountsByType(MainApp.getAccountType()); + return accounts.length > 0; + } + +} diff --git a/src/com/owncloud/android/ui/activity/LogHistoryActivity.java b/src/com/owncloud/android/ui/activity/LogHistoryActivity.java new file mode 100644 index 00000000..0aa84753 --- /dev/null +++ b/src/com/owncloud/android/ui/activity/LogHistoryActivity.java @@ -0,0 +1,120 @@ +/* ownCloud Android client application + * 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.activity; + +import java.io.File; +import java.util.ArrayList; + +import android.content.Intent; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ListView; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockPreferenceActivity; +import com.actionbarsherlock.view.MenuItem; +import com.owncloud.android.R; +import com.owncloud.android.ui.CustomButton; +import com.owncloud.android.ui.adapter.LogListAdapter; +import com.owncloud.android.utils.FileStorageUtils; + + + + +public class LogHistoryActivity extends SherlockPreferenceActivity implements OnPreferenceChangeListener { + String logpath = FileStorageUtils.getLogPath(); + File logDIR = null; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.log_send_file); + setTitle("Log History"); + ActionBar actionBar = getSherlock().getActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + ListView listView = (ListView) findViewById(android.R.id.list); + CustomButton deleteHistoryButton = (CustomButton) findViewById(R.id.deleteLogHistoryButton); + + deleteHistoryButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + File dir = new File(logpath); + if (dir != null) { + File[] files = dir.listFiles(); + if(files!=null) { + for(File f: files) { + f.delete(); + } + } + dir.delete(); + } + Intent intent = new Intent(getBaseContext(), Preferences.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + } + + }); + + + if(logpath != null){ + logDIR = new File(logpath); + } + + if(logDIR != null && logDIR.isDirectory()) { + File[] files = logDIR.listFiles(); + + if (files != null && files.length != 0) { + ArrayList logfiles_name = new ArrayList(); + for (File file : files) { + logfiles_name.add(file.getName()); + } + String[] logFiles2Array = logfiles_name.toArray(new String[logfiles_name.size()]); + LogListAdapter listadapter = new LogListAdapter(this,logFiles2Array); + listView.setAdapter(listadapter); + } + } + } + + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + super.onMenuItemSelected(featureId, item); + Intent intent; + + switch (item.getItemId()) { + + case android.R.id.home: + intent = new Intent(getBaseContext(), Preferences.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + break; + default: + return false; + } + return true; + } + @Override + public boolean onPreferenceChange(Preference arg0, Object arg1) { + return false; + } +} \ No newline at end of file diff --git a/src/com/owncloud/android/ui/activity/PinCodeActivity.java b/src/com/owncloud/android/ui/activity/PinCodeActivity.java new file mode 100644 index 00000000..068461a8 --- /dev/null +++ b/src/com/owncloud/android/ui/activity/PinCodeActivity.java @@ -0,0 +1,638 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.owncloud.android.ui.activity; + +import java.util.Arrays; + +import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.owncloud.android.R; +import com.owncloud.android.ui.CustomButton; + + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnFocusChangeListener; +import android.view.View.OnKeyListener; +import android.widget.EditText; +import android.widget.TextView; + +public class PinCodeActivity extends SherlockFragmentActivity { + + + public final static String EXTRA_ACTIVITY = "com.owncloud.android.ui.activity.PinCodeActivity.ACTIVITY"; + public final static String EXTRA_NEW_STATE = "com.owncloud.android.ui.activity.PinCodeActivity.NEW_STATE"; + + CustomButton bCancel; + TextView mPinHdr; + TextView mPinHdrExplanation; + EditText mText1; + EditText mText2; + EditText mText3; + EditText mText4; + + String [] tempText ={"","","",""}; + + String activity; + + boolean confirmingPinCode = false; + boolean pinCodeChecked = false; + boolean newPasswordEntered = false; + boolean bChange = true; // to control that only one blocks jump + int tCounter ; // Count the number of attempts an user could introduce the PIN code + + + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.pincodelock); + + Intent intent = getIntent(); + activity = intent.getStringExtra(EXTRA_ACTIVITY); + + bCancel = (CustomButton) findViewById(R.id.cancel); + mPinHdr = (TextView) findViewById(R.id.pinHdr); + mPinHdrExplanation = (TextView) findViewById(R.id.pinHdrExpl); + mText1 = (EditText) findViewById(R.id.txt1); + mText1.requestFocus(); + getWindow().setSoftInputMode(android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + mText2 = (EditText) findViewById(R.id.txt2); + mText3 = (EditText) findViewById(R.id.txt3); + mText4 = (EditText) findViewById(R.id.txt4); + + SharedPreferences appPrefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + + // Not PIN Code defined yet. + // In a previous version settings is allow from start + if ( (appPrefs.getString("PrefPinCode1", null) == null ) ){ + setChangePincodeView(true); + pinCodeChecked = true; + newPasswordEntered = true; + + }else{ + + if (appPrefs.getBoolean("set_pincode", false)){ + // pincode activated + if (activity.equals("preferences")){ + // PIN has been activated yet + mPinHdr.setText(R.string.pincode_configure_your_pin); + mPinHdrExplanation.setVisibility(View.VISIBLE); + pinCodeChecked = true ; // No need to check it + setChangePincodeView(true); + }else{ + // PIN active + bCancel.setVisibility(View.INVISIBLE); + bCancel.setVisibility(View.GONE); + mPinHdr.setText(R.string.pincode_enter_pin_code); + mPinHdrExplanation.setVisibility(View.INVISIBLE); + setChangePincodeView(false); + } + + }else { + // pincode removal + mPinHdr.setText(R.string.pincode_remove_your_pincode); + mPinHdrExplanation.setVisibility(View.INVISIBLE); + pinCodeChecked = false; + setChangePincodeView(true); + } + + } + setTextListeners(); + + + } + + + + protected void setInitVars(){ + confirmingPinCode = false; + pinCodeChecked = false; + newPasswordEntered = false; + + } + + protected void setInitView(){ + bCancel.setVisibility(View.INVISIBLE); + bCancel.setVisibility(View.GONE); + mPinHdr.setText(R.string.pincode_enter_pin_code); + mPinHdrExplanation.setVisibility(View.INVISIBLE); + } + + + protected void setChangePincodeView(boolean state){ + + if(state){ + bCancel.setVisibility(View.VISIBLE); + bCancel.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + + SharedPreferences.Editor appPrefsE = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()).edit(); + + SharedPreferences appPrefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + boolean state = appPrefs.getBoolean("set_pincode", false); + appPrefsE.putBoolean("set_pincode",!state); + appPrefsE.commit(); + setInitVars(); + finish(); + } + }); + } + + } + + + + /* + * + */ + protected void setTextListeners(){ + + /*------------------------------------------------ + * FIRST BOX + -------------------------------------------------*/ + + mText1.addTextChangedListener(new TextWatcher() { + + @Override + public void onTextChanged(CharSequence s, int start, int before, + int count) { + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + } + + @Override + public void afterTextChanged(Editable s) { + if (s.length() > 0) { + if (!confirmingPinCode){ + tempText[0] = mText1.getText().toString(); + + } + mText2.requestFocus(); + } + } + }); + + + + /*------------------------------------------------ + * SECOND BOX + -------------------------------------------------*/ + mText2.addTextChangedListener(new TextWatcher() { + + @Override + public void onTextChanged(CharSequence s, int start, int before, + int count) { + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + } + + @Override + public void afterTextChanged(Editable s) { + if (s.length() > 0) { + if (!confirmingPinCode){ + tempText[1] = mText2.getText().toString(); + } + + mText3.requestFocus(); + } + } + }); + + mText2.setOnKeyListener(new OnKeyListener() { + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL && bChange) { + + mText1.setText(""); + mText1.requestFocus(); + if (!confirmingPinCode) + tempText[0] = ""; + bChange= false; + + }else if(!bChange){ + bChange=true; + + } + return false; + } + }); + + mText2.setOnFocusChangeListener(new OnFocusChangeListener() { + + @Override + public void onFocusChange(View v, boolean hasFocus) { + mText2.setCursorVisible(true); + if (mText1.getText().toString().equals("")){ + mText2.setSelected(false); + mText2.setCursorVisible(false); + mText1.requestFocus(); + mText1.setSelected(true); + mText1.setSelection(0); + } + + } + }); + + + /*------------------------------------------------ + * THIRD BOX + -------------------------------------------------*/ + mText3.addTextChangedListener(new TextWatcher() { + + @Override + public void onTextChanged(CharSequence s, int start, int before, + int count) { + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + } + + @Override + public void afterTextChanged(Editable s) { + if (s.length() > 0) { + if (!confirmingPinCode){ + tempText[2] = mText3.getText().toString(); + } + mText4.requestFocus(); + } + } + }); + + mText3.setOnKeyListener(new OnKeyListener() { + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL && bChange) { + mText2.requestFocus(); + if (!confirmingPinCode) + tempText[1] = ""; + mText2.setText(""); + bChange= false; + + }else if(!bChange){ + bChange=true; + + } + return false; + } + }); + + mText3.setOnFocusChangeListener(new OnFocusChangeListener() { + + @Override + public void onFocusChange(View v, boolean hasFocus) { + mText3.setCursorVisible(true); + if (mText1.getText().toString().equals("")){ + mText3.setSelected(false); + mText3.setCursorVisible(false); + mText1.requestFocus(); + mText1.setSelected(true); + mText1.setSelection(0); + }else if (mText2.getText().toString().equals("")){ + mText3.setSelected(false); + mText3.setCursorVisible(false); + mText2.requestFocus(); + mText2.setSelected(true); + mText2.setSelection(0); + } + + } + }); + + /*------------------------------------------------ + * FOURTH BOX + -------------------------------------------------*/ + mText4.addTextChangedListener(new TextWatcher() { + + @Override + public void onTextChanged(CharSequence s, int start, int before, + int count) { + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + } + + @Override + public void afterTextChanged(Editable s) { + if (s.length() > 0) { + + if (!confirmingPinCode){ + tempText[3] = mText4.getText().toString(); + } + mText1.requestFocus(); + + if (!pinCodeChecked){ + pinCodeChecked = checkPincode(); + } + + if (pinCodeChecked && activity.equals("FileDisplayActivity")){ + finish(); + } else if (pinCodeChecked){ + + Intent intent = getIntent(); + String newState = intent.getStringExtra(EXTRA_NEW_STATE); + + if (newState.equals("false")){ + SharedPreferences.Editor appPrefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()).edit(); + appPrefs.putBoolean("set_pincode",false); + appPrefs.commit(); + + setInitVars(); + pinCodeEnd(false); + + }else{ + + if (!confirmingPinCode){ + pinCodeChangeRequest(); + + } else { + confirmPincode(); + } + } + + + } + } + } + }); + + + + mText4.setOnKeyListener(new OnKeyListener() { + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL && bChange) { + mText3.requestFocus(); + if (!confirmingPinCode) + tempText[2]=""; + mText3.setText(""); + bChange= false; + + }else if(!bChange){ + bChange=true; + } + return false; + } + }); + + mText4.setOnFocusChangeListener(new OnFocusChangeListener() { + + @Override + public void onFocusChange(View v, boolean hasFocus) { + mText4.setCursorVisible(true); + + if (mText1.getText().toString().equals("")){ + mText4.setSelected(false); + mText4.setCursorVisible(false); + mText1.requestFocus(); + mText1.setSelected(true); + mText1.setSelection(0); + }else if (mText2.getText().toString().equals("")){ + mText4.setSelected(false); + mText4.setCursorVisible(false); + mText2.requestFocus(); + mText2.setSelected(true); + mText2.setSelection(0); + }else if (mText3.getText().toString().equals("")){ + mText4.setSelected(false); + mText4.setCursorVisible(false); + mText3.requestFocus(); + mText3.setSelected(true); + mText3.setSelection(0); + } + + } + }); + + + + } // end setTextListener + + + protected void pinCodeChangeRequest(){ + + clearBoxes(); + mPinHdr.setText(R.string.pincode_reenter_your_pincode); + mPinHdrExplanation.setVisibility(View.INVISIBLE); + confirmingPinCode =true; + + } + + + protected boolean checkPincode(){ + + + SharedPreferences appPrefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + String pText1 = appPrefs.getString("PrefPinCode1", null); + String pText2 = appPrefs.getString("PrefPinCode2", null); + String pText3 = appPrefs.getString("PrefPinCode3", null); + String pText4 = appPrefs.getString("PrefPinCode4", null); + + if ( tempText[0].equals(pText1) && + tempText[1].equals(pText2) && + tempText[2].equals(pText3) && + tempText[3].equals(pText4) ) { + + return true; + + + }else { + Arrays.fill(tempText, null); + AlertDialog aDialog = new AlertDialog.Builder(this).create(); + CharSequence errorSeq = getString(R.string.common_error); + aDialog.setTitle(errorSeq); + CharSequence cseq = getString(R.string.pincode_wrong); + aDialog.setMessage(cseq); + CharSequence okSeq = getString(R.string.common_ok); + aDialog.setButton(okSeq, new DialogInterface.OnClickListener(){ + + @Override + public void onClick(DialogInterface dialog, int which) { + return; + } + + }); + aDialog.show(); + clearBoxes(); + mPinHdr.setText(R.string.pincode_enter_pin_code); + mPinHdrExplanation.setVisibility(View.INVISIBLE); + newPasswordEntered = true; + confirmingPinCode = false; + + } + + + return false; + } + + protected void confirmPincode(){ + + confirmingPinCode = false; + + String rText1 = mText1.getText().toString(); + String rText2 = mText2.getText().toString(); + String rText3 = mText3.getText().toString(); + String rText4 = mText4.getText().toString(); + + if ( tempText[0].equals(rText1) && + tempText[1].equals(rText2) && + tempText[2].equals(rText3) && + tempText[3].equals(rText4) ) { + + savePincodeAndExit(); + + } else { + + Arrays.fill(tempText, null); + AlertDialog aDialog = new AlertDialog.Builder(this).create(); + CharSequence errorSeq = getString(R.string.common_error); + aDialog.setTitle(errorSeq); + CharSequence cseq = getString(R.string.pincode_mismatch); + aDialog.setMessage(cseq); + CharSequence okSeq = getString(R.string.common_ok); + aDialog.setButton(okSeq, new DialogInterface.OnClickListener(){ + + @Override + public void onClick(DialogInterface dialog, int which) { + return; + } + + }); + aDialog.show(); + mPinHdr.setText(R.string.pincode_configure_your_pin); + mPinHdrExplanation.setVisibility(View.VISIBLE); + clearBoxes(); + } + + } + + + protected void pinCodeEnd(boolean state){ + AlertDialog aDialog = new AlertDialog.Builder(this).create(); + + if (state){ + CharSequence saveSeq = getString(R.string.common_save_exit); + aDialog.setTitle(saveSeq); + CharSequence cseq = getString(R.string.pincode_stored); + aDialog.setMessage(cseq); + + }else{ + CharSequence saveSeq = getString(R.string.common_save_exit); + aDialog.setTitle(saveSeq); + CharSequence cseq = getString(R.string.pincode_removed); + aDialog.setMessage(cseq); + + } + CharSequence okSeq = getString(R.string.common_ok); + aDialog.setButton(okSeq, new DialogInterface.OnClickListener(){ + + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + return; + } + + }); + aDialog.show(); + } + + protected void savePincodeAndExit(){ + SharedPreferences.Editor appPrefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()).edit(); + + appPrefs.putString("PrefPinCode1", tempText[0]); + appPrefs.putString("PrefPinCode2",tempText[1]); + appPrefs.putString("PrefPinCode3", tempText[2]); + appPrefs.putString("PrefPinCode4", tempText[3]); + appPrefs.putBoolean("set_pincode",true); + appPrefs.commit(); + + pinCodeEnd(true); + + + + } + + + protected void clearBoxes(){ + + mText1.setText(""); + mText2.setText(""); + mText3.setText(""); + mText4.setText(""); + mText1.requestFocus(); + } + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event){ + if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount()== 0){ + + if (activity.equals("preferences")){ + SharedPreferences.Editor appPrefsE = PreferenceManager + + .getDefaultSharedPreferences(getApplicationContext()).edit(); + + SharedPreferences appPrefs = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + + boolean state = appPrefs.getBoolean("set_pincode", false); + appPrefsE.putBoolean("set_pincode",!state); + appPrefsE.commit(); + setInitVars(); + finish(); + } + return true; + + } + + return super.onKeyDown(keyCode, event); + } + + + + + +} diff --git a/src/com/owncloud/android/ui/activity/Preferences.java b/src/com/owncloud/android/ui/activity/Preferences.java new file mode 100644 index 00000000..20c14bc6 --- /dev/null +++ b/src/com/owncloud/android/ui/activity/Preferences.java @@ -0,0 +1,359 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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.activity; + +import java.util.Vector; + +import android.accounts.Account; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceCategory; +import android.preference.PreferenceManager; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockPreferenceActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.owncloud.android.Log_OC; +import com.owncloud.android.OwnCloudSession; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.db.DbHandler; + + +/** + * An Activity that allows the user to change the application's settings. + * + * @author Bartek Przybylski + * + */ +public class Preferences extends SherlockPreferenceActivity implements OnPreferenceChangeListener { + + private static final String TAG = "OwnCloudPreferences"; + private final int mNewSession = 47; + private final int mEditSession = 48; + private DbHandler mDbHandler; + private Vector mSessions; + private ListPreference mTrackingUpdateInterval; + private CheckBoxPreference mDeviceTracking; + private CheckBoxPreference pCode; + //private CheckBoxPreference pLogging; + //private Preference pLoggingHistory; + private Preference pAboutApp; + private int mSelectedMenuItem; + + + @SuppressWarnings("deprecation") + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mDbHandler = new DbHandler(getBaseContext()); + mSessions = new Vector(); + addPreferencesFromResource(R.xml.preferences); + //populateAccountList(); + ActionBar actionBar = getSherlock().getActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + + Preference p = findPreference("manage_account"); + if (p != null) + p.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + Intent i = new Intent(getApplicationContext(), AccountSelectActivity.class); + startActivity(i); + return true; + } + }); + + pCode = (CheckBoxPreference) findPreference("set_pincode"); + if (pCode != null){ + pCode.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Intent i = new Intent(getApplicationContext(), PinCodeActivity.class); + i.putExtra(PinCodeActivity.EXTRA_ACTIVITY, "preferences"); + i.putExtra(PinCodeActivity.EXTRA_NEW_STATE, newValue.toString()); + startActivity(i); + + return true; + } + }); + + } + + + + PreferenceCategory preferenceCategory = (PreferenceCategory) findPreference("more"); + + boolean helpEnabled = getResources().getBoolean(R.bool.help_enabled); + Preference pHelp = findPreference("help"); + if (pHelp != null ){ + if (helpEnabled) { + pHelp.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + String helpWeb =(String) getText(R.string.url_help); + if (helpWeb != null && helpWeb.length() > 0) { + Uri uriUrl = Uri.parse(helpWeb); + Intent intent = new Intent(Intent.ACTION_VIEW, uriUrl); + startActivity(intent); + } + return true; + } + }); + } else { + preferenceCategory.removePreference(pHelp); + } + + } + + + boolean recommendEnabled = getResources().getBoolean(R.bool.recommend_enabled); + Preference pRecommend = findPreference("recommend"); + if (pRecommend != null){ + if (recommendEnabled) { + pRecommend.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + + Intent intent = new Intent(Intent.ACTION_SENDTO); + intent.setType("text/plain"); + Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(Preferences.this); + String appName = getString(R.string.app_name); + String username = currentAccount.name.substring(0, currentAccount.name.lastIndexOf('@')); + String recommendSubject = String.format(getString(R.string.recommend_subject), username, appName); + //String recommendSubject = String.format(getString(R.string.recommend_subject), appName); + intent.putExtra(Intent.EXTRA_SUBJECT, recommendSubject); + String recommendText = String.format(getString(R.string.recommend_text), getString(R.string.app_name), username); + //String recommendText = String.format(getString(R.string.recommend_text), getString(R.string.app_name), getString(R.string.url_app_download)); + intent.putExtra(Intent.EXTRA_TEXT, recommendText); + + intent.setData(Uri.parse(getString(R.string.mail_recommend))); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + + + return(true); + + } + }); + } else { + preferenceCategory.removePreference(pRecommend); + } + + } + + boolean feedbackEnabled = getResources().getBoolean(R.bool.feedback_enabled); + Preference pFeedback = findPreference("feedback"); + if (pFeedback != null){ + if (feedbackEnabled) { + pFeedback.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + String feedbackMail =(String) getText(R.string.mail_feedback); + String feedback =(String) getText(R.string.prefs_feedback); + Intent intent = new Intent(Intent.ACTION_SENDTO); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_SUBJECT, feedback); + + intent.setData(Uri.parse(feedbackMail)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + + return true; + } + }); + } else { + preferenceCategory.removePreference(pFeedback); + } + + } + + boolean imprintEnabled = getResources().getBoolean(R.bool.imprint_enabled); + Preference pImprint = findPreference("imprint"); + if (pImprint != null) { + if (imprintEnabled) { + pImprint.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + String imprintWeb = (String) getText(R.string.url_imprint); + if (imprintWeb != null && imprintWeb.length() > 0) { + Uri uriUrl = Uri.parse(imprintWeb); + Intent intent = new Intent(Intent.ACTION_VIEW, uriUrl); + startActivity(intent); + } + //ImprintDialog.newInstance(true).show(preference.get, "IMPRINT_DIALOG"); + return true; + } + }); + } else { + preferenceCategory.removePreference(pImprint); + } + } + + /* About App */ + pAboutApp = (Preference) findPreference("about_app"); + if (pAboutApp != null) { + pAboutApp.setTitle(String.format(getString(R.string.about_android), getString(R.string.app_name))); + PackageInfo pkg; + try { + pkg = getPackageManager().getPackageInfo(getPackageName(), 0); + pAboutApp.setSummary(String.format(getString(R.string.about_version), pkg.versionName)); + } catch (NameNotFoundException e) { + Log_OC.e(TAG, "Error while showing about dialog", e); + } + } + + /* DISABLED FOR RELEASE UNTIL FIXED + pLogging = (CheckBoxPreference) findPreference("log_to_file"); + if (pLogging != null) { + pLogging.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + + String logpath = Environment.getExternalStorageDirectory()+File.separator+"owncloud"+File.separator+"log"; + + if(!pLogging.isChecked()) { + Log_OC.d("Debug", "start logging"); + Log_OC.v("PATH", logpath); + Log_OC.startLogging(logpath); + } + else { + Log_OC.d("Debug", "stop logging"); + Log_OC.stopLogging(); + } + return true; + } + }); + } + + pLoggingHistory = (Preference) findPreference("log_history"); + if (pLoggingHistory != null) { + pLoggingHistory.setOnPreferenceClickListener(new OnPreferenceClickListener() { + + @Override + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(getApplicationContext(),LogHistoryActivity.class); + startActivity(intent); + return true; + } + }); + } + */ + + } + + @Override + protected void onResume() { + SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + boolean state = appPrefs.getBoolean("set_pincode", false); + pCode.setChecked(state); + super.onResume(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + return true; + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + super.onMenuItemSelected(featureId, item); + Intent intent; + + switch (item.getItemId()) { + //case R.id.addSessionItem: + case 1: + intent = new Intent(this, PreferencesNewSession.class); + startActivityForResult(intent, mNewSession); + break; + case R.id.SessionContextEdit: + intent = new Intent(this, PreferencesNewSession.class); + intent.putExtra("sessionId", mSessions.get(mSelectedMenuItem) + .getEntryId()); + intent.putExtra("sessionName", mSessions.get(mSelectedMenuItem) + .getName()); + intent.putExtra("sessionURL", mSessions.get(mSelectedMenuItem) + .getUrl()); + startActivityForResult(intent, mEditSession); + break; + case android.R.id.home: + intent = new Intent(getBaseContext(), FileDisplayActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + break; + default: + Log_OC.w(TAG, "Unknown menu item triggered"); + return false; + } + return true; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + protected void onDestroy() { + mDbHandler.close(); + super.onDestroy(); + } + + @Override + /** + * Updates various summaries after updates. Also starts and stops + * the + */ + public boolean onPreferenceChange(Preference preference, Object newValue) { + // Update current account summary + /*if (preference.equals(mAccountList)) { + mAccountList.setSummary(newValue.toString()); + } + + // Update tracking interval summary + else*/ if (preference.equals(mTrackingUpdateInterval)) { + String trackingSummary = getResources().getString( + R.string.prefs_trackmydevice_interval_summary); + trackingSummary = String.format(trackingSummary, + newValue.toString()); + mTrackingUpdateInterval.setSummary(trackingSummary); + } + + // Start or stop tracking service + else if (preference.equals(mDeviceTracking)) { + Intent locationServiceIntent = new Intent(); + locationServiceIntent + .setAction("com.owncloud.android.location.LocationLauncher"); + locationServiceIntent.putExtra("TRACKING_SETTING", + (Boolean) newValue); + sendBroadcast(locationServiceIntent); + } + return true; + } +} diff --git a/src/com/owncloud/android/ui/activity/PreferencesNewSession.java b/src/com/owncloud/android/ui/activity/PreferencesNewSession.java new file mode 100644 index 00000000..c43b29f2 --- /dev/null +++ b/src/com/owncloud/android/ui/activity/PreferencesNewSession.java @@ -0,0 +1,116 @@ +/* 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.activity; + +import android.accounts.AccountAuthenticatorActivity; +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; + +public class PreferencesNewSession extends AccountAuthenticatorActivity + implements OnClickListener { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // setContentView(R.layout.add_new_session); + /* + * EditText et;// = (EditText) + * findViewById(R.id.newSession_sessionName); + * + * et = (EditText) findViewById(R.id.newSession_URL); if + * (getIntent().hasExtra("sessionURL")) { try { URI uri = new + * URI(getIntent().getStringExtra("sessionURL")); String url = + * uri.getHost(); if (uri.getPort() != -1) { url += ":" + + * String.valueOf(uri.getPort()); } if (uri.getPath() != null) { url += + * uri.getPath(); } else { url += "/"; } et.setText(url); et = + * (EditText) findViewById(R.id.newSession_username); if + * (uri.getAuthority() != null) { if (uri.getUserInfo().indexOf(':') != + * -1) { et.setText(uri.getUserInfo().substring(0, + * uri.getUserInfo().indexOf(':'))); et = (EditText) + * findViewById(R.id.newSession_password); + * et.setText(uri.getUserInfo().substring + * (uri.getUserInfo().indexOf(':')+1)); } else { + * et.setText(uri.getUserInfo()); } } + * + * } catch (URISyntaxException e) { Log.e(TAG, "Incorrect URI syntax " + + * e.getLocalizedMessage()); } } + * + * mReturnData = new Intent(); setResult(Activity.RESULT_OK, + * mReturnData); ((Button) + * findViewById(R.id.button1)).setOnClickListener(this); ((Button) + * findViewById(R.id.button2)).setOnClickListener(this); + */ + } + + @Override + protected void onResume() { + super.onResume(); + } + + public void onClick(View v) { + /* + * switch (v.getId()) { case R.id.button1: Intent intent = new Intent(); + * if (getIntent().hasExtra("sessionId")) { intent.putExtra("sessionId", + * getIntent().getIntExtra("sessionId", -1)); } //String sessionName = + * ((EditText) + * findViewById(R.id.newSession_sessionName)).getText().toString(); // + * if (sessionName.trim().equals("") || !isNameValid(sessionName)) { // + * Toast.makeText(this, R.string.new_session_session_name_error, + * Toast.LENGTH_LONG).show(); // break; // } URI uri = prepareURI(); if + * (uri != null) { //intent.putExtra("sessionName", sessionName); + * intent.putExtra("sessionURL", uri.toString()); + * setResult(Activity.RESULT_OK, intent); AccountManager accMgr = + * AccountManager.get(this); Account a = new Account("OwnCloud", + * AccountAuthenticatorService.ACCOUNT_TYPE); + * accMgr.addAccountExplicitly(a, "asd", null); finish(); } break; case + * R.id.button2: setResult(Activity.RESULT_CANCELED); finish(); break; } + */ + } + + /* + * private URI prepareURI() { URI uri = null; String url = ""; try { String + * username = ((EditText) + * findViewById(R.id.newSession_username)).getText().toString().trim(); + * String password = ((EditText) + * findViewById(R.id.newSession_password)).getText().toString().trim(); + * String hostname = ((EditText) + * findViewById(R.id.newSession_URL)).getText().toString().trim(); String + * scheme; if (hostname.matches("[A-Za-z]://")) { scheme = + * hostname.substring(0, hostname.indexOf("://")+3); hostname = + * hostname.substring(hostname.indexOf("://")+3); } else { scheme = + * "http://"; } if (!username.equals("")) { if (!password.equals("")) { + * username += ":" + password + "@"; } else { username += "@"; } } url = + * scheme + username + hostname; Log.i(TAG, url); uri = new URI(url); } + * catch (URISyntaxException e) { Log.e(TAG, "Incorrect URI syntax " + + * e.getLocalizedMessage()); Toast.makeText(this, + * R.string.new_session_uri_error, Toast.LENGTH_LONG).show(); } return uri; + * } + * + * private boolean isNameValid(String string) { return + * string.matches("[A-Za-z0-9 _-]*"); } + */ + + @Override + public void onBackPressed() { + setResult(Activity.RESULT_CANCELED); + super.onBackPressed(); + } + +} diff --git a/src/com/owncloud/android/ui/activity/TransferServiceGetter.java b/src/com/owncloud/android/ui/activity/TransferServiceGetter.java new file mode 100644 index 00000000..fbc348d9 --- /dev/null +++ b/src/com/owncloud/android/ui/activity/TransferServiceGetter.java @@ -0,0 +1,42 @@ +/* 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.activity; + +import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; +import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; + +public interface TransferServiceGetter { + + /** + * Callback method invoked when the parent activity is fully created to get a reference to the FileDownloader service API. + * + * @return Directory to list firstly. Can be NULL. + */ + public FileDownloaderBinder getFileDownloaderBinder(); + + + /** + * Callback method invoked when the parent activity is fully created to get a reference to the FileUploader service API. + * + * @return Directory to list firstly. Can be NULL. + */ + public FileUploaderBinder getFileUploaderBinder(); + + +} diff --git a/src/com/owncloud/android/ui/activity/UploadFilesActivity.java b/src/com/owncloud/android/ui/activity/UploadFilesActivity.java new file mode 100644 index 00000000..ab7bb463 --- /dev/null +++ b/src/com/owncloud/android/ui/activity/UploadFilesActivity.java @@ -0,0 +1,395 @@ +/* ownCloud Android client application + * 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.activity; + +import java.io.File; + +import android.accounts.Account; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Environment; +import android.support.v4.app.DialogFragment; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.ActionBar.OnNavigationListener; +import com.actionbarsherlock.view.MenuItem; +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.ui.CustomButton; +import com.owncloud.android.ui.dialog.IndeterminateProgressDialog; +import com.owncloud.android.ui.fragment.ConfirmationDialogFragment; +import com.owncloud.android.ui.fragment.LocalFileListFragment; +import com.owncloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener; +import com.owncloud.android.utils.FileStorageUtils; + + +/** + * Displays local files and let the user choose what of them wants to upload + * to the current ownCloud account + * + * @author David A. Velasco + * + */ + +public class UploadFilesActivity extends FileActivity implements + LocalFileListFragment.ContainerActivity, OnNavigationListener, OnClickListener, ConfirmationDialogFragmentListener { + + private ArrayAdapter mDirectories; + private File mCurrentDir = null; + private LocalFileListFragment mFileListFragment; + private CustomButton mCancelBtn; + private CustomButton mUploadBtn; + private Account mAccountOnCreation; + private DialogFragment mCurrentDialog; + + public static final String EXTRA_CHOSEN_FILES = UploadFilesActivity.class.getCanonicalName() + ".EXTRA_CHOSEN_FILES"; + + public static final int RESULT_OK_AND_MOVE = RESULT_FIRST_USER; + + private static final String KEY_DIRECTORY_PATH = UploadFilesActivity.class.getCanonicalName() + ".KEY_DIRECTORY_PATH"; + private static final String TAG = "UploadFilesActivity"; + private static final String WAIT_DIALOG_TAG = "WAIT"; + private static final String QUERY_TO_MOVE_DIALOG_TAG = "QUERY_TO_MOVE"; + + + @Override + public void onCreate(Bundle savedInstanceState) { + Log_OC.d(TAG, "onCreate() start"); + super.onCreate(savedInstanceState); + + if(savedInstanceState != null) { + mCurrentDir = new File(savedInstanceState.getString(UploadFilesActivity.KEY_DIRECTORY_PATH)); + } else { + mCurrentDir = Environment.getExternalStorageDirectory(); + } + + mAccountOnCreation = getAccount(); + + /// USER INTERFACE + + // Drop-down navigation + mDirectories = new CustomArrayAdapter(this, R.layout.sherlock_spinner_dropdown_item); + File currDir = mCurrentDir; + while(currDir != null && currDir.getParentFile() != null) { + mDirectories.add(currDir.getName()); + currDir = currDir.getParentFile(); + } + mDirectories.add(File.separator); + + // Inflate and set the layout view + setContentView(R.layout.upload_files_layout); + mFileListFragment = (LocalFileListFragment) getSupportFragmentManager().findFragmentById(R.id.local_files_list); + + + // Set input controllers + mCancelBtn = (CustomButton) findViewById(R.id.upload_files_btn_cancel); + mCancelBtn.setOnClickListener(this); + mUploadBtn = (CustomButton) findViewById(R.id.upload_files_btn_upload); + mUploadBtn.setOnClickListener(this); + + + // Action bar setup + ActionBar actionBar = getSupportActionBar(); + actionBar.setHomeButtonEnabled(true); // mandatory since Android ICS, according to the official documentation + actionBar.setDisplayHomeAsUpEnabled(mCurrentDir != null && mCurrentDir.getName() != null); + actionBar.setDisplayShowTitleEnabled(false); + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); + actionBar.setListNavigationCallbacks(mDirectories, this); + + // wait dialog + if (mCurrentDialog != null) { + mCurrentDialog.dismiss(); + mCurrentDialog = null; + } + + Log_OC.d(TAG, "onCreate() end"); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + boolean retval = true; + switch (item.getItemId()) { + case android.R.id.home: { + if(mCurrentDir != null && mCurrentDir.getParentFile() != null){ + onBackPressed(); + } + break; + } + default: + retval = super.onOptionsItemSelected(item); + } + return retval; + } + + + @Override + public boolean onNavigationItemSelected(int itemPosition, long itemId) { + int i = itemPosition; + while (i-- != 0) { + onBackPressed(); + } + // the next operation triggers a new call to this method, but it's necessary to + // ensure that the name exposed in the action bar is the current directory when the + // user selected it in the navigation list + if (itemPosition != 0) + getSupportActionBar().setSelectedNavigationItem(0); + return true; + } + + + @Override + public void onBackPressed() { + if (mDirectories.getCount() <= 1) { + finish(); + return; + } + popDirname(); + mFileListFragment.onNavigateUp(); + mCurrentDir = mFileListFragment.getCurrentDirectory(); + + if(mCurrentDir.getParentFile() == null){ + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayHomeAsUpEnabled(false); + } + } + + + @Override + protected void onSaveInstanceState(Bundle outState) { + // responsibility of restore is preferred in onCreate() before than in onRestoreInstanceState when there are Fragments involved + Log_OC.d(TAG, "onSaveInstanceState() start"); + super.onSaveInstanceState(outState); + outState.putString(UploadFilesActivity.KEY_DIRECTORY_PATH, mCurrentDir.getAbsolutePath()); + Log_OC.d(TAG, "onSaveInstanceState() end"); + } + + + /** + * Pushes a directory to the drop down list + * @param directory to push + * @throws IllegalArgumentException If the {@link File#isDirectory()} returns false. + */ + public void pushDirname(File directory) { + if(!directory.isDirectory()){ + throw new IllegalArgumentException("Only directories may be pushed!"); + } + mDirectories.insert(directory.getName(), 0); + mCurrentDir = directory; + } + + /** + * Pops a directory name from the drop down list + * @return True, unless the stack is empty + */ + public boolean popDirname() { + mDirectories.remove(mDirectories.getItem(0)); + return !mDirectories.isEmpty(); + } + + + // Custom array adapter to override text colors + private class CustomArrayAdapter extends ArrayAdapter { + + public CustomArrayAdapter(UploadFilesActivity ctx, int view) { + super(ctx, view); + } + + public View getView(int position, View convertView, ViewGroup parent) { + View v = super.getView(position, convertView, parent); + + ((TextView) v).setTextColor(getResources().getColorStateList( + android.R.color.white)); + return v; + } + + public View getDropDownView(int position, View convertView, + ViewGroup parent) { + View v = super.getDropDownView(position, convertView, parent); + + ((TextView) v).setTextColor(getResources().getColorStateList( + android.R.color.white)); + + return v; + } + + } + + /** + * {@inheritDoc} + */ + @Override + public void onDirectoryClick(File directory) { + pushDirname(directory); + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + } + + + /** + * {@inheritDoc} + */ + @Override + public void onFileClick(File file) { + // nothing to do + } + + /** + * {@inheritDoc} + */ + @Override + public File getInitialDirectory() { + return mCurrentDir; + } + + + /** + * Performs corresponding action when user presses 'Cancel' or 'Upload' button + * + * TODO Make here the real request to the Upload service ; will require to receive the account and + * target folder where the upload must be done in the received intent. + */ + @Override + public void onClick(View v) { + if (v.getId() == R.id.upload_files_btn_cancel) { + setResult(RESULT_CANCELED); + finish(); + + } else if (v.getId() == R.id.upload_files_btn_upload) { + new CheckAvailableSpaceTask().execute(); + } + } + + + /** + * Asynchronous task checking if there is space enough to copy all the files chosen + * to upload into the ownCloud local folder. + * + * Maybe an AsyncTask is not strictly necessary, but who really knows. + * + * @author David A. Velasco + */ + private class CheckAvailableSpaceTask extends AsyncTask { + + /** + * Updates the UI before trying the movement + */ + @Override + protected void onPreExecute () { + /// progress dialog and disable 'Move' button + mCurrentDialog = IndeterminateProgressDialog.newInstance(R.string.wait_a_moment, false); + mCurrentDialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG); + } + + + /** + * Checks the available space + * + * @return 'True' if there is space enough. + */ + @Override + protected Boolean doInBackground(Void... params) { + String[] checkedFilePaths = mFileListFragment.getCheckedFilePaths(); + long total = 0; + for (int i=0; checkedFilePaths != null && i < checkedFilePaths.length ; i++) { + String localPath = checkedFilePaths[i]; + File localFile = new File(localPath); + total += localFile.length(); + } + return (FileStorageUtils.getUsableSpace(mAccountOnCreation.name) >= total); + } + + /** + * 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. + * + * @param result 'True' when there is space enough to copy all the selected files. + */ + @Override + protected void onPostExecute(Boolean result) { + mCurrentDialog.dismiss(); + mCurrentDialog = null; + + if (result) { + // return the list of selected files (success) + Intent data = new Intent(); + data.putExtra(EXTRA_CHOSEN_FILES, mFileListFragment.getCheckedFilePaths()); + setResult(RESULT_OK, data); + finish(); + + } else { + // show a dialog to query the user if wants to move the selected files 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); + dialog.setOnConfirmationListener(UploadFilesActivity.this); + dialog.show(getSupportFragmentManager(), QUERY_TO_MOVE_DIALOG_TAG); + } + } + } + + @Override + public void onConfirmation(String callerTag) { + Log_OC.d(TAG, "Positive button in dialog was clicked; dialog tag is " + callerTag); + if (callerTag.equals(QUERY_TO_MOVE_DIALOG_TAG)) { + // return the list of selected files to the caller activity (success), signaling that they should be moved to the ownCloud folder, instead of copied + Intent data = new Intent(); + data.putExtra(EXTRA_CHOSEN_FILES, mFileListFragment.getCheckedFilePaths()); + setResult(RESULT_OK_AND_MOVE, data); + finish(); + } + } + + + @Override + public void onNeutral(String callerTag) { + Log_OC.d(TAG, "Phantom neutral button in dialog was clicked; dialog tag is " + callerTag); + } + + + @Override + public void onCancel(String callerTag) { + /// nothing to do; don't finish, let the user change the selection + Log_OC.d(TAG, "Negative button in dialog was clicked; dialog tag is " + callerTag); + } + + + @Override + protected void onAccountSet(boolean stateWasRecovered) { + if (getAccount() != null) { + if (!mAccountOnCreation.equals(getAccount())) { + setResult(RESULT_CANCELED); + finish(); + } + + } else { + Log_OC.wtf(TAG, "onAccountChanged was called with NULL account associated!"); + setResult(RESULT_CANCELED); + finish(); + } + } + + +} diff --git a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java new file mode 100644 index 00000000..a2651fac --- /dev/null +++ b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java @@ -0,0 +1,210 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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.adapter; + +import android.accounts.Account; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; + + +import java.util.Vector; + +import com.owncloud.android.DisplayUtils; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.datamodel.DataStorageManager; +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.ui.activity.TransferServiceGetter; + + +/** + * This Adapter populates a ListView with all files and folders in an ownCloud + * instance. + * + * @author Bartek Przybylski + * + */ +public class FileListListAdapter extends BaseAdapter implements ListAdapter { + private Context mContext; + private OCFile mFile = null; + private Vector mFiles = null; + private DataStorageManager mStorageManager; + private Account mAccount; + private TransferServiceGetter mTransferServiceGetter; + + public FileListListAdapter(Context context, TransferServiceGetter transferServiceGetter) { + mContext = context; + mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext); + mTransferServiceGetter = transferServiceGetter; + } + + @Override + public boolean areAllItemsEnabled() { + return true; + } + + @Override + public boolean isEnabled(int position) { + return true; + } + + @Override + public int getCount() { + return mFiles != null ? mFiles.size() : 0; + } + + @Override + public Object getItem(int position) { + if (mFiles == null || mFiles.size() <= position) + return null; + return mFiles.get(position); + } + + @Override + public long getItemId(int position) { + if (mFiles == null || mFiles.size() <= position) + return 0; + return mFiles.get(position).getFileId(); + } + + @Override + public int getItemViewType(int position) { + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view = convertView; + if (view == null) { + LayoutInflater inflator = (LayoutInflater) mContext + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + view = inflator.inflate(R.layout.list_item, null); + } + + if (mFiles != null && mFiles.size() > position) { + OCFile file = mFiles.get(position); + TextView fileName = (TextView) view.findViewById(R.id.Filename); + String name = file.getFileName(); + + fileName.setText(name); + ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1); + fileIcon.setImageResource(DisplayUtils.getResourceId(file.getMimetype())); + ImageView localStateView = (ImageView) view.findViewById(R.id.imageView2); + FileDownloaderBinder downloaderBinder = mTransferServiceGetter.getFileDownloaderBinder(); + FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder(); + if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) { + localStateView.setImageResource(R.drawable.downloading_file_indicator); + localStateView.setVisibility(View.VISIBLE); + } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file)) { + localStateView.setImageResource(R.drawable.uploading_file_indicator); + localStateView.setVisibility(View.VISIBLE); + } else if (file.isDown()) { + localStateView.setImageResource(R.drawable.local_file_indicator); + localStateView.setVisibility(View.VISIBLE); + } else { + localStateView.setVisibility(View.INVISIBLE); + } + + TextView fileSizeV = (TextView) view.findViewById(R.id.file_size); + TextView lastModV = (TextView) view.findViewById(R.id.last_mod); + ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox); + + if (!file.isDirectory()) { + fileSizeV.setVisibility(View.VISIBLE); + fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength())); + lastModV.setVisibility(View.VISIBLE); + lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.getModificationTimestamp())); + // this if-else is needed even thoe fav icon is visible by default + // because android reuses views in listview + if (!file.keepInSync()) { + view.findViewById(R.id.imageView3).setVisibility(View.GONE); + } else { + view.findViewById(R.id.imageView3).setVisibility(View.VISIBLE); + } + + ListView parentList = (ListView)parent; + if (parentList.getChoiceMode() == ListView.CHOICE_MODE_NONE) { + checkBoxV.setVisibility(View.GONE); + } else { + if (parentList.isItemChecked(position)) { + checkBoxV.setImageResource(android.R.drawable.checkbox_on_background); + } else { + checkBoxV.setImageResource(android.R.drawable.checkbox_off_background); + } + checkBoxV.setVisibility(View.VISIBLE); + } + + } + else { + + fileSizeV.setVisibility(View.VISIBLE); + fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength())); + lastModV.setVisibility(View.VISIBLE); + lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.getModificationTimestamp())); + checkBoxV.setVisibility(View.GONE); + view.findViewById(R.id.imageView3).setVisibility(View.GONE); + } + } + + return view; + } + + @Override + public int getViewTypeCount() { + return 1; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public boolean isEmpty() { + return (mFiles == null || mFiles.isEmpty()); + } + + /** + * Change the adapted directory for a new one + * @param directory New file to adapt. Can be NULL, meaning "no content to adapt". + * @param updatedStorageManager Optional updated storage manager; used to replace mStorageManager if is different (and not NULL) + */ + public void swapDirectory(OCFile directory, DataStorageManager updatedStorageManager) { + mFile = directory; + if (updatedStorageManager != null && updatedStorageManager != mStorageManager) { + mStorageManager = updatedStorageManager; + mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext); + } + if (mStorageManager != null) { + mFiles = mStorageManager.getDirectoryContent(mFile); + } else { + mFiles = null; + } + notifyDataSetChanged(); + } + +} diff --git a/src/com/owncloud/android/ui/adapter/LandingScreenAdapter.java b/src/com/owncloud/android/ui/adapter/LandingScreenAdapter.java new file mode 100644 index 00000000..cea48941 --- /dev/null +++ b/src/com/owncloud/android/ui/adapter/LandingScreenAdapter.java @@ -0,0 +1,113 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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.adapter; + + +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.ui.activity.Preferences; + +import android.content.Context; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +/** + * Populates the landing screen icons. + * + * @author Lennart Rosam + * + */ +public class LandingScreenAdapter extends BaseAdapter { + + private Context mContext; + + private final Integer[] mLandingScreenIcons = { R.drawable.home, + R.drawable.music, R.drawable.contacts, R.drawable.calendar, + android.R.drawable.ic_menu_agenda, R.drawable.settings }; + + private final Integer[] mLandingScreenTexts = { R.string.main_files, + R.string.main_music, R.string.main_contacts, + R.string.main_calendar, R.string.main_bookmarks, + R.string.main_settings }; + + public LandingScreenAdapter(Context context) { + mContext = context; + } + + @Override + public int getCount() { + return mLandingScreenIcons.length; + } + + @Override + /** + * Returns the Intent associated with this object + * or null if the functionality is not yet implemented + */ + public Object getItem(int position) { + Intent intent = new Intent(); + + switch (position) { + case 0: + /* + * The FileDisplayActivity requires the ownCloud account as an + * parcableExtra. We will put in the one that is selected in the + * preferences + */ + intent.setClass(mContext, FileDisplayActivity.class); + intent.putExtra("ACCOUNT", + AccountUtils.getCurrentOwnCloudAccount(mContext)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + break; + case 5: + intent.setClass(mContext, Preferences.class); + break; + default: + intent = null; + } + return intent; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + LayoutInflater inflator = LayoutInflater.from(mContext); + convertView = inflator.inflate(R.layout.landing_page_item, null); + + ImageView icon = (ImageView) convertView + .findViewById(R.id.gridImage); + TextView iconText = (TextView) convertView + .findViewById(R.id.gridText); + + icon.setImageResource(mLandingScreenIcons[position]); + iconText.setText(mLandingScreenTexts[position]); + } + return convertView; + } +} diff --git a/src/com/owncloud/android/ui/adapter/LocalFileListAdapter.java b/src/com/owncloud/android/ui/adapter/LocalFileListAdapter.java new file mode 100644 index 00000000..ff883c56 --- /dev/null +++ b/src/com/owncloud/android/ui/adapter/LocalFileListAdapter.java @@ -0,0 +1,185 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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.adapter; + +import java.io.File; +import java.util.Arrays; +import java.util.Comparator; + +import com.owncloud.android.DisplayUtils; +import com.owncloud.android.R; + + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; + +/** + * This Adapter populates a ListView with all files and directories contained + * in a local directory + * + * @author David A. Velasco + * + */ +public class LocalFileListAdapter extends BaseAdapter implements ListAdapter { + + private Context mContext; + private File mDirectory; + private File[] mFiles = null; + + public LocalFileListAdapter(File directory, Context context) { + mContext = context; + swapDirectory(directory); + } + + @Override + public boolean areAllItemsEnabled() { + return true; + } + + @Override + public boolean isEnabled(int position) { + return true; + } + + @Override + public int getCount() { + return mFiles != null ? mFiles.length : 0; + } + + @Override + public Object getItem(int position) { + if (mFiles == null || mFiles.length <= position) + return null; + return mFiles[position]; + } + + @Override + public long getItemId(int position) { + return mFiles != null && mFiles.length <= position ? position : -1; + } + + @Override + public int getItemViewType(int position) { + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view = convertView; + if (view == null) { + LayoutInflater inflator = (LayoutInflater) mContext + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + view = inflator.inflate(R.layout.list_item, null); + } + if (mFiles != null && mFiles.length > position) { + File file = mFiles[position]; + + TextView fileName = (TextView) view.findViewById(R.id.Filename); + String name = file.getName(); + fileName.setText(name); + + ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1); + if (!file.isDirectory()) { + fileIcon.setImageResource(R.drawable.file); + } else { + fileIcon.setImageResource(R.drawable.ic_menu_archive); + } + + TextView fileSizeV = (TextView) view.findViewById(R.id.file_size); + TextView lastModV = (TextView) view.findViewById(R.id.last_mod); + ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox); + if (!file.isDirectory()) { + fileSizeV.setVisibility(View.VISIBLE); + fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.length())); + lastModV.setVisibility(View.VISIBLE); + lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.lastModified())); + ListView parentList = (ListView)parent; + if (parentList.getChoiceMode() == ListView.CHOICE_MODE_NONE) { + checkBoxV.setVisibility(View.GONE); + } else { + if (parentList.isItemChecked(position)) { + checkBoxV.setImageResource(android.R.drawable.checkbox_on_background); + } else { + checkBoxV.setImageResource(android.R.drawable.checkbox_off_background); + } + checkBoxV.setVisibility(View.VISIBLE); + } + + } else { + fileSizeV.setVisibility(View.GONE); + lastModV.setVisibility(View.GONE); + checkBoxV.setVisibility(View.GONE); + } + + view.findViewById(R.id.imageView2).setVisibility(View.INVISIBLE); // not GONE; the alignment changes; ugly way to keep it + view.findViewById(R.id.imageView3).setVisibility(View.GONE); + } + + return view; + } + + @Override + public int getViewTypeCount() { + return 1; + } + + @Override + public boolean hasStableIds() { + return false; + } + + @Override + public boolean isEmpty() { + return (mFiles == null || mFiles.length == 0); + } + + /** + * Change the adapted directory for a new one + * @param directory New file to adapt. Can be NULL, meaning "no content to adapt". + */ + public void swapDirectory(File directory) { + mDirectory = directory; + mFiles = (mDirectory != null ? mDirectory.listFiles() : null); + if (mFiles != null) { + Arrays.sort(mFiles, new Comparator() { + @Override + public int compare(File lhs, File rhs) { + if (lhs.isDirectory() && !rhs.isDirectory()) { + return -1; + } else if (!lhs.isDirectory() && rhs.isDirectory()) { + return 1; + } + return compareNames(lhs, rhs); + } + + private int compareNames(File lhs, File rhs) { + return lhs.getName().toLowerCase().compareTo(rhs.getName().toLowerCase()); + } + + }); + } + notifyDataSetChanged(); + } +} diff --git a/src/com/owncloud/android/ui/adapter/LogListAdapter.java b/src/com/owncloud/android/ui/adapter/LogListAdapter.java new file mode 100644 index 00000000..ae4335ef --- /dev/null +++ b/src/com/owncloud/android/ui/adapter/LogListAdapter.java @@ -0,0 +1,55 @@ +package com.owncloud.android.ui.adapter; + +import java.io.File; + +import com.owncloud.android.R; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Environment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + + + +public class LogListAdapter extends ArrayAdapter { + private Context context = null; + private String[] values; + private Uri fileUri = null; + + + public LogListAdapter(Context context, String[] values) { + super(context, R.layout.log_item, values); + this.context = context; + this.values = values; + } + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View rowView = inflater.inflate(R.layout.log_item, parent, false); + TextView listText = (TextView) rowView.findViewById(R.id.log_item_single); + listText.setText(values[position]); + listText.setTextSize(15); + fileUri = Uri.fromFile(new File(Environment.getExternalStorageDirectory()+File.separator+"owncloud"+File.separator+"log"+File.separator+values[position])); + listText.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND); + emailIntent.setType("text/rtf"); + emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "OwnCloud Logfile"); + emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, "This is a automatic E-mail send by owncloud/android"); + emailIntent.putExtra(android.content.Intent.EXTRA_STREAM, fileUri); + emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(Intent.createChooser(emailIntent, "Send mail...")); + } + }); + return rowView; + } +} diff --git a/src/com/owncloud/android/ui/dialog/ChangelogDialog.java b/src/com/owncloud/android/ui/dialog/ChangelogDialog.java new file mode 100644 index 00000000..676bea57 --- /dev/null +++ b/src/com/owncloud/android/ui/dialog/ChangelogDialog.java @@ -0,0 +1,104 @@ +/* ownCloud Android client application + * 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.dialog; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.webkit.WebView; + +import com.actionbarsherlock.app.SherlockDialogFragment; +import com.owncloud.android.R; + + +/** + * Dialog to show the contents of res/raw/CHANGELOG.txt + */ +public class ChangelogDialog extends SherlockDialogFragment { + + private static final String ARG_CANCELABLE = ChangelogDialog.class.getCanonicalName() + ".ARG_CANCELABLE"; + + + /** + * Public factory method to get dialog instances. + * + * @param cancelable If 'true', the dialog can be cancelled by the user input (BACK button, touch outside...) + * @return New dialog instance, ready to show. + */ + public static ChangelogDialog newInstance(boolean cancelable) { + ChangelogDialog fragment = new ChangelogDialog(); + Bundle args = new Bundle(); + args.putBoolean(ARG_CANCELABLE, cancelable); + fragment.setArguments(args); + return fragment; + } + + + /** + * {@inheritDoc} + */ + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + /// load the custom view to insert in the dialog, between title and + WebView webview = new WebView(getActivity()); + webview.loadUrl("file:///android_res/raw/" + getResources().getResourceEntryName(R.raw.changelog) + ".html"); + + /// build the dialog + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + + Dialog dialog = builder.setView(webview) + .setIcon(R.drawable.icon) + //.setTitle(R.string.whats_new) + .setPositiveButton(R.string.common_ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }).create(); + + dialog.setCancelable(getArguments().getBoolean(ARG_CANCELABLE)); + return dialog; + } + + /** + * {@inheritDoc} + *-/ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + /// load the custom layout + View view = inflater.inflate(R.layout.fragment_changelog, container); + mEditText = (EditText) view.findViewById(R.id.txt_your_name); + getDialog().setTitle(R.string.whats_new); + + /// read full contents of the change log file (don't make it too big) + InputStream changeLogStream = getResources().openRawResource(R.raw.changelog); + Scanner scanner = new java.util.Scanner(changeLogStream).useDelimiter("\\A"); + String text = scanner.hasNext() ? scanner.next() : ""; + + /// make clickable the links in the change log file + SpannableString sText = new SpannableString(text); + Linkify.addLinks(sText, Linkify.ALL); + + return view; + } + */ +} + + diff --git a/src/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java b/src/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java new file mode 100644 index 00000000..1998fcb8 --- /dev/null +++ b/src/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java @@ -0,0 +1,121 @@ +/* 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.dialog; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; + +import com.actionbarsherlock.app.SherlockDialogFragment; +import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.owncloud.android.R; + + +/** + * Dialog which will be displayed to user upon keep-in-sync file conflict. + * + * @author Bartek Przybylski + * + */ +public class ConflictsResolveDialog extends SherlockDialogFragment { + + public static enum Decision { + CANCEL, + KEEP_BOTH, + OVERWRITE + } + + OnConflictDecisionMadeListener mListener; + + public static ConflictsResolveDialog newInstance(String path, OnConflictDecisionMadeListener listener) { + ConflictsResolveDialog f = new ConflictsResolveDialog(); + Bundle args = new Bundle(); + args.putString("remotepath", path); + f.setArguments(args); + f.mListener = listener; + return f; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + String remotepath = getArguments().getString("remotepath"); + return new AlertDialog.Builder(getSherlockActivity()) + .setIcon(R.drawable.icon) + .setTitle(R.string.conflict_title) + .setMessage(String.format(getString(R.string.conflict_message), remotepath)) + .setPositiveButton(R.string.conflict_overwrite, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + if (mListener != null) + mListener.ConflictDecisionMade(Decision.OVERWRITE); + } + }) + .setNeutralButton(R.string.conflict_keep_both, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (mListener != null) + mListener.ConflictDecisionMade(Decision.KEEP_BOTH); + } + }) + .setNegativeButton(R.string.conflict_dont_upload, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (mListener != null) + mListener.ConflictDecisionMade(Decision.CANCEL); + } + }) + .create(); + } + + public void showDialog(SherlockFragmentActivity activity) { + Fragment prev = activity.getSupportFragmentManager().findFragmentByTag("dialog"); + FragmentTransaction ft = activity.getSupportFragmentManager().beginTransaction(); + if (prev != null) { + ft.remove(prev); + } + ft.addToBackStack(null); + + this.show(ft, "dialog"); + } + + public void dismissDialog(SherlockFragmentActivity activity) { + Fragment prev = activity.getSupportFragmentManager().findFragmentByTag(getTag()); + if (prev != null) { + FragmentTransaction ft = activity.getSupportFragmentManager().beginTransaction(); + ft.remove(prev); + ft.commit(); + } + } + + @Override + public void onCancel(DialogInterface dialog) { + mListener.ConflictDecisionMade(Decision.CANCEL); + } + + public interface OnConflictDecisionMadeListener { + public void ConflictDecisionMade(Decision decision); + } +} diff --git a/src/com/owncloud/android/ui/dialog/EditNameDialog.java b/src/com/owncloud/android/ui/dialog/EditNameDialog.java new file mode 100644 index 00000000..4d6243af --- /dev/null +++ b/src/com/owncloud/android/ui/dialog/EditNameDialog.java @@ -0,0 +1,173 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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.dialog; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager.LayoutParams; +import android.widget.EditText; +import android.widget.TextView; + +import com.actionbarsherlock.app.SherlockDialogFragment; +import com.owncloud.android.R; + + + +/** + * Dialog to request the user to input a name, optionally initialized with a former name. + * + * @author Bartek Przybylski + * @author David A. Velasco + */ +public class EditNameDialog extends SherlockDialogFragment implements DialogInterface.OnClickListener { + + public static final String TAG = EditNameDialog.class.getSimpleName(); + + protected static final String ARG_TITLE = "TITLE"; + protected static final String ARG_NAME = "NAME"; + protected static final String ARG_SELECTION_START = "SELECTION_START"; + protected static final String ARG_SELECTION_END = "SELECTION_END"; + + private String mNewFilename; + private boolean mResult; + private EditNameDialogListener mListener; + + /** + * Public factory method to get dialog instances. + * + * @param title Text to show as title in the dialog. + * @param name Optional text to include in the text input field when the dialog is shown. + * @param listener Instance to notify when the dialog is dismissed. + * @param selectionStart Index to the first character to be selected in the input field; negative value for none + * @param selectionEnd Index to the last character to be selected in the input field; negative value for none + * @return New dialog instance, ready to show. + */ + static public EditNameDialog newInstance(String title, String name, int selectionStart, int selectionEnd, EditNameDialogListener listener) { + EditNameDialog f = new EditNameDialog(); + Bundle args = new Bundle(); + args.putString(ARG_TITLE, title); + args.putString(ARG_NAME, name); + args.putInt(ARG_SELECTION_START, selectionStart); + args.putInt(ARG_SELECTION_END, selectionEnd); + f.setArguments(args); + f.setOnDismissListener(listener); + return f; + } + + + /** + * {@inheritDoc} + */ + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + String currentName = getArguments().getString(ARG_NAME); + if (currentName == null) + currentName = ""; + String title = getArguments().getString(ARG_TITLE); + + // Inflate the layout for the dialog + LayoutInflater inflater = getSherlockActivity().getLayoutInflater(); + View v = inflater.inflate(R.layout.edit_box_dialog, null); // null parent view because it will go in the dialog layout + EditText inputText = ((EditText)v.findViewById(R.id.user_input)); + inputText.setText(currentName); + + // Set it to the dialog + AlertDialog.Builder builder = new AlertDialog.Builder(getSherlockActivity()); + builder.setView(v) + .setPositiveButton(R.string.common_ok, this) + .setNegativeButton(R.string.common_cancel, this); + + if (title != null) { + builder.setTitle(title); + } + + mResult = false; + + Dialog d = builder.create(); + + inputText.requestFocus(); + int selectionStart = getArguments().getInt(ARG_SELECTION_START, -1); + int selectionEnd = getArguments().getInt(ARG_SELECTION_END, -1); + if (selectionStart >= 0 && selectionEnd >= 0) { + inputText.setSelection(Math.min(selectionStart, selectionEnd), Math.max(selectionStart, selectionEnd)); + } + d.getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE); + return d; + } + + + /** + * Performs the corresponding action when a dialog button is clicked. + * + * Saves the text in the input field to be accessed through {@link #getNewFilename()} when the positive + * button is clicked. + * + * Notify the current listener in any case. + */ + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case AlertDialog.BUTTON_POSITIVE: { + mNewFilename = ((TextView)(getDialog().findViewById(R.id.user_input))).getText().toString(); + mResult = true; + } + case AlertDialog.BUTTON_NEGATIVE: { // fall through + dismiss(); + if (mListener != null) + mListener.onDismiss(this); + } + } + } + + protected void setOnDismissListener(EditNameDialogListener listener) { + mListener = listener; + } + + /** + * Returns the text in the input field after the user clicked the positive button. + * + * @return Text in the input field. + */ + public String getNewFilename() { + return mNewFilename; + } + + /** + * + * @return True when the user clicked the positive button. + */ + public boolean getResult() { + return mResult; + } + + + /** + * Interface to receive a notification when any button in the dialog is clicked. + */ + public interface EditNameDialogListener { + public void onDismiss(EditNameDialog dialog); + } + + +} + diff --git a/src/com/owncloud/android/ui/dialog/IndeterminateProgressDialog.java b/src/com/owncloud/android/ui/dialog/IndeterminateProgressDialog.java new file mode 100644 index 00000000..dbd3d991 --- /dev/null +++ b/src/com/owncloud/android/ui/dialog/IndeterminateProgressDialog.java @@ -0,0 +1,92 @@ +/* ownCloud Android client application + * 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.dialog; + +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.DialogInterface.OnKeyListener; +import android.os.Bundle; +import android.view.KeyEvent; + +import com.actionbarsherlock.app.SherlockDialogFragment; +import com.owncloud.android.R; + + +public class IndeterminateProgressDialog extends SherlockDialogFragment { + + private static final String ARG_MESSAGE_ID = IndeterminateProgressDialog.class.getCanonicalName() + ".ARG_MESSAGE_ID"; + private static final String ARG_CANCELABLE = IndeterminateProgressDialog.class.getCanonicalName() + ".ARG_CANCELABLE"; + + + /** + * Public factory method to get dialog instances. + * + * @param messageId Resource id for a message to show in the dialog. + * @param cancelable If 'true', the dialog can be cancelled by the user input (BACK button, touch outside...) + * @return New dialog instance, ready to show. + */ + public static IndeterminateProgressDialog newInstance(int messageId, boolean cancelable) { + IndeterminateProgressDialog fragment = new IndeterminateProgressDialog(); + Bundle args = new Bundle(); + args.putInt(ARG_MESSAGE_ID, messageId); + args.putBoolean(ARG_CANCELABLE, cancelable); + fragment.setArguments(args); + return fragment; + } + + + /** + * {@inheritDoc} + */ + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + /// create indeterminate progress dialog + final ProgressDialog dialog = new ProgressDialog(getActivity()); + dialog.setIndeterminate(true); + + /// set message + int messageId = getArguments().getInt(ARG_MESSAGE_ID, R.string.placeholder_sentence); + dialog.setMessage(getString(messageId)); + + /// set cancellation behavior + boolean cancelable = getArguments().getBoolean(ARG_CANCELABLE, false); + if (!cancelable) { + dialog.setCancelable(false); + // disable the back button + OnKeyListener keyListener = new OnKeyListener() { + @Override + public boolean onKey(DialogInterface dialog, int keyCode, + KeyEvent event) { + + if( keyCode == KeyEvent.KEYCODE_BACK){ + return true; + } + return false; + } + + }; + dialog.setOnKeyListener(keyListener); + } + + return dialog; + } + +} + + diff --git a/src/com/owncloud/android/ui/dialog/LoadingDialog.java b/src/com/owncloud/android/ui/dialog/LoadingDialog.java new file mode 100644 index 00000000..2203e039 --- /dev/null +++ b/src/com/owncloud/android/ui/dialog/LoadingDialog.java @@ -0,0 +1,58 @@ +package com.owncloud.android.ui.dialog; + +import com.owncloud.android.R; + +import android.app.Dialog; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.TextView; + +public class LoadingDialog extends DialogFragment { + + private String mMessage; + + public LoadingDialog() { + super(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + setCancelable(false); + } + + public LoadingDialog(String message) { + this.mMessage = message; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + // Create a view by inflating desired layout + View v = inflater.inflate(R.layout.loading_dialog, container, false); + + // set value + TextView tv = (TextView) v.findViewById(R.id.loadingText); + tv.setText(mMessage); + + return v; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + return dialog; + } + + @Override + public void onDestroyView() { + if (getDialog() != null && getRetainInstance()) + getDialog().setDismissMessage(null); + super.onDestroyView(); + } +} diff --git a/src/com/owncloud/android/ui/dialog/SamlWebViewDialog.java b/src/com/owncloud/android/ui/dialog/SamlWebViewDialog.java new file mode 100644 index 00000000..516fce10 --- /dev/null +++ b/src/com/owncloud/android/ui/dialog/SamlWebViewDialog.java @@ -0,0 +1,285 @@ +/* ownCloud Android client application + * 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.dialog; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Handler; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.app.FragmentManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.CookieManager; +import android.webkit.CookieSyncManager; +import android.webkit.WebBackForwardList; +import android.webkit.WebSettings; +import android.webkit.WebView; + +import com.actionbarsherlock.app.SherlockDialogFragment; +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.authentication.SsoWebViewClient; +import com.owncloud.android.authentication.SsoWebViewClient.SsoWebViewClientListener; + + +import eu.alefzero.webdav.WebdavClient; + +/** + * Dialog to show the WebView for SAML Authentication + * + * @author Maria Asensio + * @author David A. Velasco + */ +public class SamlWebViewDialog extends SherlockDialogFragment { + + public final String SAML_DIALOG_TAG = "SamlWebViewDialog"; + + private final static String TAG = SamlWebViewDialog.class.getSimpleName(); + + private static final String ARG_INITIAL_URL = "INITIAL_URL"; + private static final String ARG_TARGET_URL = "TARGET_URL"; + private static final String KEY_WEBVIEW_STATE = "WEBVIEW_STATE"; + + private WebView mSsoWebView; + private SsoWebViewClient mWebViewClient; + + private String mInitialUrl; + private String mTargetUrl; + + private Handler mHandler; + + private SsoWebViewClientListener mSsoWebViewClientListener; + + //private View mSsoRootView; + + + /** + * Public factory method to get dialog instances. + * + * @param handler + * @param Url Url to open at WebView + * @param targetURL mHostBaseUrl + AccountUtils.getWebdavPath(mDiscoveredVersion, mCurrentAuthTokenType) + * @return New dialog instance, ready to show. + */ + public static SamlWebViewDialog newInstance(String url, String targetUrl) { + Log_OC.d(TAG, "New instance"); + SamlWebViewDialog fragment = new SamlWebViewDialog(); + Bundle args = new Bundle(); + args.putString(ARG_INITIAL_URL, url); + args.putString(ARG_TARGET_URL, targetUrl); + fragment.setArguments(args); + return fragment; + } + + + public SamlWebViewDialog() { + super(); + Log_OC.d(TAG, "constructor"); + } + + + @Override + public void onAttach(Activity activity) { + Log_OC.d(TAG, "onAttach"); + super.onAttach(activity); + try { + mSsoWebViewClientListener = (SsoWebViewClientListener) activity; + mHandler = new Handler(); + mWebViewClient = new SsoWebViewClient(mHandler, mSsoWebViewClientListener); + + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement " + SsoWebViewClientListener.class.getSimpleName()); + } + } + + + @SuppressLint("SetJavaScriptEnabled") + @Override + public void onCreate(Bundle savedInstanceState) { + Log_OC.d(TAG, "onCreate"); + super.onCreate(savedInstanceState); + + CookieSyncManager.createInstance(getActivity()); + + if (savedInstanceState == null) { + mInitialUrl = getArguments().getString(ARG_INITIAL_URL); + mTargetUrl = getArguments().getString(ARG_TARGET_URL); + } else { + mInitialUrl = savedInstanceState.getString(ARG_INITIAL_URL); + mTargetUrl = savedInstanceState.getString(ARG_TARGET_URL); + } + + setStyle(SherlockDialogFragment.STYLE_NO_TITLE, R.style.Theme_ownCloud_Dialog); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Log_OC.d(TAG, "onCreateDialog"); + + /* + // build the dialog + AlertDialog.Builder builder = new AlertDialog.Builder(getSherlockActivity()); + if (mSsoRootView.getParent() != null) { + ((ViewGroup)(mSsoRootView.getParent())).removeView(mSsoRootView); + } + builder.setView(mSsoRootView); + //builder.setView(mSsoWebView); + Dialog dialog = builder.create(); + */ + + return super.onCreateDialog(savedInstanceState); + } + + @SuppressLint("SetJavaScriptEnabled") + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Log_OC.d(TAG, "onCreateView"); + + // Inflate layout of the dialog + View rootView = inflater.inflate(R.layout.sso_dialog, container, false); // null parent view because it will go in the dialog layout + mSsoWebView = (WebView) rootView.findViewById(R.id.sso_webview); + + mWebViewClient.setTargetUrl(mTargetUrl); + mSsoWebView.setWebViewClient(mWebViewClient); + + if (savedInstanceState == null) { + Log_OC.d(TAG, " initWebView start"); + CookieManager cookieManager = CookieManager.getInstance(); + cookieManager.setAcceptCookie(true); + cookieManager.removeAllCookie(); + mSsoWebView.loadUrl(mInitialUrl); + + } else { + Log_OC.d(TAG, " restoreWebView start"); + WebBackForwardList history = mSsoWebView.restoreState(savedInstanceState.getBundle(KEY_WEBVIEW_STATE)); + if (history == null) { + Log_OC.e(TAG, "Error restoring WebView state ; back to starting URL"); + mSsoWebView.loadUrl(mInitialUrl); + } + } + + WebSettings webSettings = mSsoWebView.getSettings(); + webSettings.setJavaScriptEnabled(true); + webSettings.setBuiltInZoomControls(true); + webSettings.setLoadWithOverviewMode(false); + webSettings.setSavePassword(false); + webSettings.setUserAgentString(WebdavClient.USER_AGENT); + webSettings.setSaveFormData(false); + + return rootView; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + Log_OC.d(SAML_DIALOG_TAG, "onSaveInstanceState being CALLED"); + super.onSaveInstanceState(outState); + + // save URLs + outState.putString(ARG_INITIAL_URL, mInitialUrl); + outState.putString(ARG_TARGET_URL, mTargetUrl); + + // Save the state of the WebView + Bundle webviewState = new Bundle(); + mSsoWebView.saveState(webviewState); + outState.putBundle(KEY_WEBVIEW_STATE, webviewState); + } + + @Override + public void onDestroyView() { + Log_OC.d(TAG, "onDestroyView"); + + mSsoWebView.setWebViewClient(null); + + // Work around bug: http://code.google.com/p/android/issues/detail?id=17423 + Dialog dialog = getDialog(); + if ((dialog != null)) { + dialog.setOnDismissListener(null); + //dialog.dismiss(); + //dialog.setDismissMessage(null); + } + + super.onDestroyView(); + } + + @Override + public void onDestroy() { + Log_OC.d(TAG, "onDestroy"); + super.onDestroy(); + } + + @Override + public void onDetach() { + Log_OC.d(TAG, "onDetach"); + mSsoWebViewClientListener = null; + mWebViewClient = null; + super.onDetach(); + } + + @Override + public void onCancel (DialogInterface dialog) { + Log_OC.d(SAML_DIALOG_TAG, "onCancel"); + super.onCancel(dialog); + } + + @Override + public void onDismiss (DialogInterface dialog) { + Log_OC.d(SAML_DIALOG_TAG, "onDismiss"); + super.onDismiss(dialog); + } + + @Override + public void onStart() { + Log_OC.d(SAML_DIALOG_TAG, "onStart"); + super.onStart(); + } + + @Override + public void onStop() { + Log_OC.d(SAML_DIALOG_TAG, "onStop"); + super.onStop(); + } + + @Override + public void onResume() { + Log_OC.d(SAML_DIALOG_TAG, "onResume"); + super.onResume(); + } + + @Override + public void onPause() { + Log_OC.d(SAML_DIALOG_TAG, "onPause"); + super.onPause(); + } + + @Override + public int show (FragmentTransaction transaction, String tag) { + Log_OC.d(SAML_DIALOG_TAG, "show (transaction)"); + return super.show(transaction, tag); + } + + @Override + public void show (FragmentManager manager, String tag) { + Log_OC.d(SAML_DIALOG_TAG, "show (manager)"); + super.show(manager, tag); + } + +} \ No newline at end of file diff --git a/src/com/owncloud/android/ui/dialog/SslValidatorDialog.java b/src/com/owncloud/android/ui/dialog/SslValidatorDialog.java new file mode 100644 index 00000000..82ecd6bc --- /dev/null +++ b/src/com/owncloud/android/ui/dialog/SslValidatorDialog.java @@ -0,0 +1,356 @@ +/* ownCloud Android client application + * 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.dialog; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import javax.security.auth.x500.X500Principal; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.network.CertificateCombinedException; +import com.owncloud.android.network.OwnCloudClientUtils; +import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.ui.CustomButton; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.view.View; +import android.view.Window; +import android.widget.TextView; + + +/** + * Dialog to request the user about a certificate that could not be validated with the certificates store in the system. + * + * @author David A. Velasco + */ +public class SslValidatorDialog extends Dialog { + + private final static String TAG = SslValidatorDialog.class.getSimpleName(); + + private OnSslValidatorListener mListener; + private CertificateCombinedException mException = null; + private View mView; + + + /** + * Creates a new SslValidatorDialog to ask the user if an untrusted certificate from a server should + * be trusted. + * + * @param context Android context where the dialog will live. + * @param result Result of a failed remote operation. + * @param listener Object to notice when the server certificate was added to the local certificates store. + * @return A new SslValidatorDialog instance. NULL if the operation can not be recovered + * by setting the certificate as reliable. + */ + public static SslValidatorDialog newInstance(Context context, RemoteOperationResult result, OnSslValidatorListener listener) { + if (result != null && result.isSslRecoverableException()) { + SslValidatorDialog dialog = new SslValidatorDialog(context, listener); + return dialog; + } else { + return null; + } + } + + /** + * Private constructor. + * + * Instances have to be created through static {@link SslValidatorDialog#newInstance}. + * + * @param context Android context where the dialog will live + * @param e Exception causing the need of prompt the user about the server certificate. + * @param listener Object to notice when the server certificate was added to the local certificates store. + */ + private SslValidatorDialog(Context context, OnSslValidatorListener listener) { + super(context); + mListener = listener; + } + + + /** + * {@inheritDoc} + */ + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); + mView = getLayoutInflater().inflate(R.layout.ssl_validator_layout, null); + setContentView(mView); + + mView.findViewById(R.id.ok).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + try { + saveServerCert(); + dismiss(); + if (mListener != null) + mListener.onSavedCertificate(); + else + Log_OC.d(TAG, "Nobody there to notify the certificate was saved"); + + } catch (GeneralSecurityException e) { + dismiss(); + if (mListener != null) + mListener.onFailedSavingCertificate(); + Log_OC.e(TAG, "Server certificate could not be saved in the known servers trust store ", e); + + } catch (IOException e) { + dismiss(); + if (mListener != null) + mListener.onFailedSavingCertificate(); + Log_OC.e(TAG, "Server certificate could not be saved in the known servers trust store ", e); + } + } + }); + + mView.findViewById(R.id.cancel).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + cancel(); + } + }); + + mView.findViewById(R.id.details_btn).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + View detailsScroll = findViewById(R.id.details_scroll); + if (detailsScroll.getVisibility() == View.VISIBLE) { + detailsScroll.setVisibility(View.GONE); + ((CustomButton)v).setText(R.string.ssl_validator_btn_details_see); + + } else { + detailsScroll.setVisibility(View.VISIBLE); + ((CustomButton)v).setText(R.string.ssl_validator_btn_details_hide); + } + } + }); + } + + + public void updateResult(RemoteOperationResult result) { + if (result.isSslRecoverableException()) { + mException = (CertificateCombinedException) result.getException(); + + /// clean + mView.findViewById(R.id.reason_cert_not_trusted).setVisibility(View.GONE); + mView.findViewById(R.id.reason_cert_expired).setVisibility(View.GONE); + mView.findViewById(R.id.reason_cert_not_yet_valid).setVisibility(View.GONE); + mView.findViewById(R.id.reason_hostname_not_verified).setVisibility(View.GONE); + mView.findViewById(R.id.details_scroll).setVisibility(View.GONE); + + /// refresh + if (mException.getCertPathValidatorException() != null) { + ((TextView)mView.findViewById(R.id.reason_cert_not_trusted)).setVisibility(View.VISIBLE); + } + + if (mException.getCertificateExpiredException() != null) { + ((TextView)mView.findViewById(R.id.reason_cert_expired)).setVisibility(View.VISIBLE); + } + + if (mException.getCertificateNotYetValidException() != null) { + ((TextView)mView.findViewById(R.id.reason_cert_not_yet_valid)).setVisibility(View.VISIBLE); + } + + if (mException.getSslPeerUnverifiedException() != null ) { + ((TextView)mView.findViewById(R.id.reason_hostname_not_verified)).setVisibility(View.VISIBLE); + } + + + showCertificateData(mException.getServerCertificate()); + } + + } + + private void showCertificateData(X509Certificate cert) { + + if (cert != null) { + showSubject(cert.getSubjectX500Principal()); + showIssuer(cert.getIssuerX500Principal()); + showValidity(cert.getNotBefore(), cert.getNotAfter()); + showSignature(cert); + + } else { + // this should not happen + // TODO + } + } + + private void showSignature(X509Certificate cert) { + TextView sigView = ((TextView)mView.findViewById(R.id.value_signature)); + TextView algorithmView = ((TextView)mView.findViewById(R.id.value_signature_algorithm)); + sigView.setText(getHex(cert.getSignature())); + algorithmView.setText(cert.getSigAlgName()); + } + + public String getHex(final byte [] raw) { + if (raw == null) { + return null; + } + final StringBuilder hex = new StringBuilder(2 * raw.length); + for (final byte b : raw) { + final int hiVal = (b & 0xF0) >> 4; + final int loVal = b & 0x0F; + hex.append((char) ('0' + (hiVal + (hiVal / 10 * 7)))); + hex.append((char) ('0' + (loVal + (loVal / 10 * 7)))); + } + return hex.toString(); + } + + private void showValidity(Date notBefore, Date notAfter) { + TextView fromView = ((TextView)mView.findViewById(R.id.value_validity_from)); + TextView toView = ((TextView)mView.findViewById(R.id.value_validity_to)); + fromView.setText(notBefore.toLocaleString()); + toView.setText(notAfter.toLocaleString()); + } + + private void showSubject(X500Principal subject) { + Map s = parsePrincipal(subject); + TextView cnView = ((TextView)mView.findViewById(R.id.value_subject_CN)); + TextView oView = ((TextView)mView.findViewById(R.id.value_subject_O)); + TextView ouView = ((TextView)mView.findViewById(R.id.value_subject_OU)); + TextView cView = ((TextView)mView.findViewById(R.id.value_subject_C)); + TextView stView = ((TextView)mView.findViewById(R.id.value_subject_ST)); + TextView lView = ((TextView)mView.findViewById(R.id.value_subject_L)); + + if (s.get("CN") != null) { + cnView.setText(s.get("CN")); + cnView.setVisibility(View.VISIBLE); + } else { + cnView.setVisibility(View.GONE); + } + if (s.get("O") != null) { + oView.setText(s.get("O")); + oView.setVisibility(View.VISIBLE); + } else { + oView.setVisibility(View.GONE); + } + if (s.get("OU") != null) { + ouView.setText(s.get("OU")); + ouView.setVisibility(View.VISIBLE); + } else { + ouView.setVisibility(View.GONE); + } + if (s.get("C") != null) { + cView.setText(s.get("C")); + cView.setVisibility(View.VISIBLE); + } else { + cView.setVisibility(View.GONE); + } + if (s.get("ST") != null) { + stView.setText(s.get("ST")); + stView.setVisibility(View.VISIBLE); + } else { + stView.setVisibility(View.GONE); + } + if (s.get("L") != null) { + lView.setText(s.get("L")); + lView.setVisibility(View.VISIBLE); + } else { + lView.setVisibility(View.GONE); + } + } + + private void showIssuer(X500Principal issuer) { + Map s = parsePrincipal(issuer); + TextView cnView = ((TextView)mView.findViewById(R.id.value_issuer_CN)); + TextView oView = ((TextView)mView.findViewById(R.id.value_issuer_O)); + TextView ouView = ((TextView)mView.findViewById(R.id.value_issuer_OU)); + TextView cView = ((TextView)mView.findViewById(R.id.value_issuer_C)); + TextView stView = ((TextView)mView.findViewById(R.id.value_issuer_ST)); + TextView lView = ((TextView)mView.findViewById(R.id.value_issuer_L)); + + if (s.get("CN") != null) { + cnView.setText(s.get("CN")); + cnView.setVisibility(View.VISIBLE); + } else { + cnView.setVisibility(View.GONE); + } + if (s.get("O") != null) { + oView.setText(s.get("O")); + oView.setVisibility(View.VISIBLE); + } else { + oView.setVisibility(View.GONE); + } + if (s.get("OU") != null) { + ouView.setText(s.get("OU")); + ouView.setVisibility(View.VISIBLE); + } else { + ouView.setVisibility(View.GONE); + } + if (s.get("C") != null) { + cView.setText(s.get("C")); + cView.setVisibility(View.VISIBLE); + } else { + cView.setVisibility(View.GONE); + } + if (s.get("ST") != null) { + stView.setText(s.get("ST")); + stView.setVisibility(View.VISIBLE); + } else { + stView.setVisibility(View.GONE); + } + if (s.get("L") != null) { + lView.setText(s.get("L")); + lView.setVisibility(View.VISIBLE); + } else { + lView.setVisibility(View.GONE); + } + } + + + private Map parsePrincipal(X500Principal principal) { + Map result = new HashMap(); + String toParse = principal.getName(); + String[] pieces = toParse.split(","); + String[] tokens = {"CN", "O", "OU", "C", "ST", "L"}; + for (int i=0; i < pieces.length ; i++) { + for (int j=0; j= android.os.Build.VERSION_CODES.HONEYCOMB) { + builder.setIconAttribute(android.R.attr.alertDialogIcon); + } + + if (posBtn != -1) + builder.setPositiveButton(posBtn, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + mListener.onConfirmation(getTag()); + dialog.dismiss(); + } + }); + if (neuBtn != -1) + builder.setNeutralButton(neuBtn, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + mListener.onNeutral(getTag()); + dialog.dismiss(); + } + }); + if (negBtn != -1) + builder.setNegativeButton(negBtn, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mListener.onCancel(getTag()); + dialog.dismiss(); + } + }); + return builder.create(); + } + + + public interface ConfirmationDialogFragmentListener { + public void onConfirmation(String callerTag); + public void onNeutral(String callerTag); + public void onCancel(String callerTag); + } + +} + diff --git a/src/com/owncloud/android/ui/fragment/ExtendedListFragment.java b/src/com/owncloud/android/ui/fragment/ExtendedListFragment.java new file mode 100644 index 00000000..a57fb095 --- /dev/null +++ b/src/com/owncloud/android/ui/fragment/ExtendedListFragment.java @@ -0,0 +1,119 @@ +/* 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 com.actionbarsherlock.app.SherlockFragment; +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.ui.ExtendedListView; + + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ListAdapter; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; + +/** + * TODO extending SherlockListFragment instead of SherlockFragment + */ +public class ExtendedListFragment extends SherlockFragment implements OnItemClickListener { + + private static final String TAG = ExtendedListFragment.class.getSimpleName(); + + private static final String KEY_SAVED_LIST_POSITION = "SAVED_LIST_POSITION"; + + protected ExtendedListView mList; + + public void setListAdapter(ListAdapter listAdapter) { + mList.setAdapter(listAdapter); + mList.invalidate(); + } + + public ListView getListView() { + return mList; + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Log_OC.e(TAG, "onCreateView"); + //mList = new ExtendedListView(getActivity()); + View v = inflater.inflate(R.layout.list_fragment, null); + mList = (ExtendedListView)(v.findViewById(R.id.list_root)); + mList.setOnItemClickListener(this); + //mList.setEmptyView(v.findViewById(R.id.empty_list_view)); // looks like it's not a cool idea + 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); + } + + return v; + } + + + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + super.onSaveInstanceState(savedInstanceState); + Log_OC.e(TAG, "onSaveInstanceState()"); + savedInstanceState.putInt(KEY_SAVED_LIST_POSITION, getReferencePosition()); + } + + + /** + * Calculates the position of the item that will be used as a reference to reposition the visible items in the list when + * the device is turned to other position. + * + * THe current policy is take as a reference the visible item in the center of the screen. + * + * @return The position in the list of the visible item in the center of the screen. + */ + protected int getReferencePosition() { + if (mList != null) { + return (mList.getFirstVisiblePosition() + mList.getLastVisiblePosition()) / 2; + } else { + return 0; + } + } + + + /** + * Sets the visible part of the list from the reference position. + * + * @param position Reference position previously returned by {@link LocalFileListFragment#getReferencePosition()} + */ + protected void setReferencePosition(int position) { + if (mList != null) { + mList.setAndCenterSelection(position); + } + } + + @Override + public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) { + // to be @overriden + } + + +} diff --git a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java new file mode 100644 index 00000000..36d7c217 --- /dev/null +++ b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -0,0 +1,930 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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 java.io.File; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +import android.accounts.Account; +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.os.Handler; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; +import com.owncloud.android.DisplayUtils; +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.services.FileObserverService; +import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; +import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; +import com.owncloud.android.operations.OnRemoteOperationListener; +import com.owncloud.android.operations.RemoteOperation; +import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.operations.RemoveFileOperation; +import com.owncloud.android.operations.RenameFileOperation; +import com.owncloud.android.operations.SynchronizeFileOperation; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.ui.activity.ConflictsResolveActivity; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.ui.dialog.EditNameDialog; +import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener; +import com.owncloud.android.ui.preview.PreviewImageFragment; + + +import eu.alefzero.webdav.OnDatatransferProgressListener; + +/** + * This Fragment is used to display the details about a file. + * + * @author Bartek Przybylski + * @author David A. Velasco + */ +public class FileDetailFragment extends FileFragment implements + OnClickListener, + ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener, EditNameDialogListener { + + private FileFragment.ContainerActivity mContainerActivity; + + private int mLayout; + private View mView; + private Account mAccount; + private FileDataStorageManager mStorageManager; + + private UploadFinishReceiver mUploadFinishReceiver; + public ProgressListener mProgressListener; + + private Handler mHandler; + private RemoteOperation mLastRemoteOperation; + + private static final String TAG = FileDetailFragment.class.getSimpleName(); + public static final String FTAG_CONFIRMATION = "REMOVE_CONFIRMATION_FRAGMENT"; + + + /** + * Creates an empty details fragment. + * + * It's necessary to keep a public constructor without parameters; the system uses it when tries to reinstantiate a fragment automatically. + */ + public FileDetailFragment() { + super(); + mAccount = null; + mStorageManager = null; + mLayout = R.layout.file_details_empty; + mProgressListener = null; + } + + /** + * Creates a details fragment. + * + * When 'fileToDetail' or 'ocAccount' are null, creates a dummy layout (to use when a file wasn't tapped before). + * + * @param fileToDetail An {@link OCFile} to show in the fragment + * @param ocAccount An ownCloud account; needed to start downloads + */ + public FileDetailFragment(OCFile fileToDetail, Account ocAccount) { + super(fileToDetail); + mAccount = ocAccount; + mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment + mLayout = R.layout.file_details_empty; + mProgressListener = null; + } + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mHandler = new Handler(); + setHasOptionsMenu(true); + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + //super.onCreateView(inflater, container, savedInstanceState); + + if (savedInstanceState != null) { + setFile((OCFile)savedInstanceState.getParcelable(FileActivity.EXTRA_FILE)); + mAccount = savedInstanceState.getParcelable(FileActivity.EXTRA_ACCOUNT); + } + + if(getFile() != null && mAccount != null) { + mLayout = R.layout.file_details_fragment; + } + + View view = null; + //view = inflater.inflate(mLayout, container, false); + view = inflater.inflate(mLayout, null); + mView = view; + + if (mLayout == R.layout.file_details_fragment) { + mView.findViewById(R.id.fdKeepInSync).setOnClickListener(this); + ProgressBar progressBar = (ProgressBar)mView.findViewById(R.id.fdProgressBar); + mProgressListener = new ProgressListener(progressBar); + mView.findViewById(R.id.fdCancelBtn).setOnClickListener(this); + } + + updateFileDetails(false, false); + return view; + } + + /** + * {@inheritDoc} + */ + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mContainerActivity = (ContainerActivity) activity; + + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement " + FileDetailFragment.ContainerActivity.class.getSimpleName()); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (mAccount != null) { + mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver()); + } + } + + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(FileActivity.EXTRA_FILE, getFile()); + outState.putParcelable(FileActivity.EXTRA_ACCOUNT, mAccount); + } + + @Override + public void onStart() { + super.onStart(); + listenForTransferProgress(); + } + + @Override + public void onResume() { + super.onResume(); + mUploadFinishReceiver = new UploadFinishReceiver(); + FileUploader fileUploader = new FileUploader(); + IntentFilter filter = new IntentFilter(fileUploader.getUploadFinishMessage()); + getActivity().registerReceiver(mUploadFinishReceiver, filter); + + } + + + @Override + public void onPause() { + super.onPause(); + if (mUploadFinishReceiver != null) { + getActivity().unregisterReceiver(mUploadFinishReceiver); + mUploadFinishReceiver = null; + } + } + + + @Override + public void onStop() { + super.onStop(); + leaveTransferProgress(); + } + + + @Override + public View getView() { + return super.getView() == null ? mView : super.getView(); + } + + + /** + * {@inheritDoc} + */ + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.file_actions_menu, menu); + MenuItem item = menu.findItem(R.id.action_see_details); + if (item != null) { + item.setVisible(false); + item.setEnabled(false); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public void onPrepareOptionsMenu (Menu menu) { + super.onPrepareOptionsMenu(menu); + + List toHide = new ArrayList(); + List toShow = new ArrayList(); + OCFile file = getFile(); + + FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); + boolean downloading = downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file); + FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); + boolean uploading = uploaderBinder != null && uploaderBinder.isUploading(mAccount, getFile()); + + if (downloading || uploading) { + toHide.add(R.id.action_download_file); + toHide.add(R.id.action_rename_file); + toHide.add(R.id.action_remove_file); + toHide.add(R.id.action_open_file_with); + if (!downloading) { + toHide.add(R.id.action_cancel_download); + toShow.add(R.id.action_cancel_upload); + } else { + toHide.add(R.id.action_cancel_upload); + toShow.add(R.id.action_cancel_download); + } + + } else if (file != null && file.isDown()) { + toHide.add(R.id.action_download_file); + toHide.add(R.id.action_cancel_download); + toHide.add(R.id.action_cancel_upload); + + toShow.add(R.id.action_rename_file); + toShow.add(R.id.action_remove_file); + toShow.add(R.id.action_open_file_with); + toShow.add(R.id.action_sync_file); + + } else if (file != null) { + toHide.add(R.id.action_open_file_with); + toHide.add(R.id.action_cancel_download); + toHide.add(R.id.action_cancel_upload); + toHide.add(R.id.action_sync_file); + + toShow.add(R.id.action_rename_file); + toShow.add(R.id.action_remove_file); + toShow.add(R.id.action_download_file); + + } else { + toHide.add(R.id.action_open_file_with); + toHide.add(R.id.action_cancel_download); + toHide.add(R.id.action_cancel_upload); + toHide.add(R.id.action_sync_file); + toHide.add(R.id.action_download_file); + toHide.add(R.id.action_rename_file); + toHide.add(R.id.action_remove_file); + + } + + MenuItem item = null; + for (int i : toHide) { + item = menu.findItem(i); + if (item != null) { + item.setVisible(false); + item.setEnabled(false); + } + } + for (int i : toShow) { + item = menu.findItem(i); + if (item != null) { + item.setVisible(true); + item.setEnabled(true); + } + } + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_open_file_with: { + mContainerActivity.openFile(getFile()); + return true; + } + case R.id.action_remove_file: { + removeFile(); + return true; + } + case R.id.action_rename_file: { + renameFile(); + return true; + } + case R.id.action_download_file: + case R.id.action_cancel_download: + case R.id.action_cancel_upload: + case R.id.action_sync_file: { + synchronizeFile(); + return true; + } + default: + return false; + } + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.fdKeepInSync: { + toggleKeepInSync(); + break; + } + case R.id.fdCancelBtn: { + synchronizeFile(); + break; + } + default: + Log_OC.e(TAG, "Incorrect view clicked!"); + } + } + + + private void toggleKeepInSync() { + CheckBox cb = (CheckBox) getView().findViewById(R.id.fdKeepInSync); + OCFile file = getFile(); + file.setKeepInSync(cb.isChecked()); + mStorageManager.saveFile(file); + + /// register the OCFile instance in the observer service to monitor local updates; + /// if necessary, the file is download + Intent intent = new Intent(getActivity().getApplicationContext(), + FileObserverService.class); + intent.putExtra(FileObserverService.KEY_FILE_CMD, + (cb.isChecked()? + FileObserverService.CMD_ADD_OBSERVED_FILE: + FileObserverService.CMD_DEL_OBSERVED_FILE)); + intent.putExtra(FileObserverService.KEY_CMD_ARG_FILE, file); + intent.putExtra(FileObserverService.KEY_CMD_ARG_ACCOUNT, mAccount); + getActivity().startService(intent); + + if (file.keepInSync()) { + synchronizeFile(); // force an immediate synchronization + } + } + + + private void removeFile() { + OCFile file = getFile(); + ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance( + R.string.confirmation_remove_alert, + new String[]{file.getFileName()}, + file.isDown() ? R.string.confirmation_remove_remote_and_local : R.string.confirmation_remove_remote, + file.isDown() ? R.string.confirmation_remove_local : -1, + R.string.common_cancel); + confDialog.setOnConfirmationListener(this); + confDialog.show(getFragmentManager(), FTAG_CONFIRMATION); + } + + + private void renameFile() { + OCFile file = getFile(); + String fileName = file.getFileName(); + int extensionStart = file.isDirectory() ? -1 : fileName.lastIndexOf("."); + int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length(); + EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this); + dialog.show(getFragmentManager(), "nameeditdialog"); + } + + private void synchronizeFile() { + OCFile file = getFile(); + FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); + FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); + if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) { + downloaderBinder.cancel(mAccount, file); + if (file.isDown()) { + setButtonsForDown(); + } else { + setButtonsForRemote(); + } + + } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file)) { + uploaderBinder.cancel(mAccount, file); + if (!file.fileExists()) { + // TODO make something better + ((FileDisplayActivity)getActivity()).cleanSecondFragment(); + + } else if (file.isDown()) { + setButtonsForDown(); + } else { + setButtonsForRemote(); + } + + } else { + mLastRemoteOperation = new SynchronizeFileOperation(file, null, mStorageManager, mAccount, true, false, getActivity()); + mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); + + // update ui + ((FileDisplayActivity) getActivity()).showLoadingDialog(); + + } + } + + @Override + public void onConfirmation(String callerTag) { + OCFile file = getFile(); + if (callerTag.equals(FTAG_CONFIRMATION)) { + if (mStorageManager.getFileById(file.getFileId()) != null) { + mLastRemoteOperation = new RemoveFileOperation( file, + true, + mStorageManager); + mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); + + ((FileDisplayActivity) getActivity()).showLoadingDialog(); + } + } + } + + @Override + public void onNeutral(String callerTag) { + File f = null; + OCFile file = getFile(); + if (file.isDown() && (f = new File(file.getStoragePath())).exists()) { + f.delete(); + file.setStoragePath(null); + mStorageManager.saveFile(file); + updateFileDetails(file, mAccount); + } + } + + @Override + public void onCancel(String callerTag) { + Log_OC.d(TAG, "REMOVAL CANCELED"); + } + + + /** + * Check if the fragment was created with an empty layout. An empty fragment can't show file details, must be replaced. + * + * @return True when the fragment was created with the empty layout. + */ + public boolean isEmpty() { + return (mLayout == R.layout.file_details_empty || getFile() == null || mAccount == null); + } + + + /** + * Use this method to signal this Activity that it shall update its view. + * + * @param file : An {@link OCFile} + */ + public void updateFileDetails(OCFile file, Account ocAccount) { + setFile(file); + if (ocAccount != null && ( + mStorageManager == null || + (mAccount != null && !mAccount.equals(ocAccount)) + )) { + mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver()); + } + mAccount = ocAccount; + updateFileDetails(false, false); + } + + /** + * Updates the view with all relevant details about that file. + * + * TODO Remove parameter when the transferring state of files is kept in database. + * + * TODO REFACTORING! this method called 5 times before every time the fragment is shown! + * + * @param transferring Flag signaling if the file should be considered as downloading or uploading, + * although {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and + * {@link FileUploaderBinder#isUploading(Account, OCFile)} return false. + * + * @param refresh If 'true', try to refresh the hold file from the database + */ + public void updateFileDetails(boolean transferring, boolean refresh) { + + if (readyToShow()) { + + if (refresh && mStorageManager != null) { + setFile(mStorageManager.getFileByPath(getFile().getRemotePath())); + } + OCFile file = getFile(); + + // set file details + setFilename(file.getFileName()); + setFiletype(file.getMimetype()); + setFilesize(file.getFileLength()); + if(ocVersionSupportsTimeCreated()){ + setTimeCreated(file.getCreationTimestamp()); + } + + setTimeModified(file.getModificationTimestamp()); + + CheckBox cb = (CheckBox)getView().findViewById(R.id.fdKeepInSync); + cb.setChecked(file.keepInSync()); + + // configure UI for depending upon local state of the file + //if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath()) || FileUploader.isUploading(mAccount, mFile.getRemotePath())) { + FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); + FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); + if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file))) { + setButtonsForTransferring(); + + } else if (file.isDown()) { + + setButtonsForDown(); + + } else { + // TODO load default preview image; when the local file is removed, the preview remains there + setButtonsForRemote(); + } + } + getView().invalidate(); + } + + /** + * Checks if the fragment is ready to show details of a OCFile + * + * @return 'True' when the fragment is ready to show details of a file + */ + private boolean readyToShow() { + return (getFile() != null && mAccount != null && mLayout == R.layout.file_details_fragment); + } + + + /** + * Updates the filename in view + * @param filename to set + */ + private void setFilename(String filename) { + TextView tv = (TextView) getView().findViewById(R.id.fdFilename); + if (tv != null) + tv.setText(filename); + } + + /** + * Updates the MIME type in view + * @param mimetype to set + */ + private void setFiletype(String mimetype) { + TextView tv = (TextView) getView().findViewById(R.id.fdType); + if (tv != null) { + String printableMimetype = DisplayUtils.convertMIMEtoPrettyPrint(mimetype);; + tv.setText(printableMimetype); + } + ImageView iv = (ImageView) getView().findViewById(R.id.fdIcon); + if (iv != null) { + iv.setImageResource(DisplayUtils.getResourceId(mimetype)); + } + } + + /** + * Updates the file size in view + * @param filesize in bytes to set + */ + private void setFilesize(long filesize) { + TextView tv = (TextView) getView().findViewById(R.id.fdSize); + if (tv != null) + tv.setText(DisplayUtils.bytesToHumanReadable(filesize)); + } + + /** + * Updates the time that the file was created in view + * @param milliseconds Unix time to set + */ + private void setTimeCreated(long milliseconds){ + TextView tv = (TextView) getView().findViewById(R.id.fdCreated); + TextView tvLabel = (TextView) getView().findViewById(R.id.fdCreatedLabel); + if(tv != null){ + tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds)); + tv.setVisibility(View.VISIBLE); + tvLabel.setVisibility(View.VISIBLE); + } + } + + /** + * Updates the time that the file was last modified + * @param milliseconds Unix time to set + */ + private void setTimeModified(long milliseconds){ + TextView tv = (TextView) getView().findViewById(R.id.fdModified); + if(tv != null){ + tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds)); + } + } + + /** + * Enables or disables buttons for a file being downloaded + */ + private void setButtonsForTransferring() { + if (!isEmpty()) { + // let's protect the user from himself ;) + getView().findViewById(R.id.fdKeepInSync).setEnabled(false); + + // show the progress bar for the transfer + getView().findViewById(R.id.fdProgressBlock).setVisibility(View.VISIBLE); + TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText); + progressText.setVisibility(View.VISIBLE); + FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); + FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); + if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile())) { + progressText.setText(R.string.downloader_download_in_progress_ticker); + } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, getFile())) { + progressText.setText(R.string.uploader_upload_in_progress_ticker); + } + } + } + + /** + * Enables or disables buttons for a file locally available + */ + private void setButtonsForDown() { + if (!isEmpty()) { + getView().findViewById(R.id.fdKeepInSync).setEnabled(true); + + // hides the progress bar + getView().findViewById(R.id.fdProgressBlock).setVisibility(View.GONE); + TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText); + progressText.setVisibility(View.GONE); + } + } + + /** + * Enables or disables buttons for a file not locally available + */ + private void setButtonsForRemote() { + if (!isEmpty()) { + getView().findViewById(R.id.fdKeepInSync).setEnabled(true); + + // hides the progress bar + getView().findViewById(R.id.fdProgressBlock).setVisibility(View.GONE); + TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText); + progressText.setVisibility(View.GONE); + } + } + + + /** + * In ownCloud 3.X.X and 4.X.X there is a bug that SabreDAV does not return + * the time that the file was created. There is a chance that this will + * be fixed in future versions. Use this method to check if this version of + * ownCloud has this fix. + * @return True, if ownCloud the ownCloud version is supporting creation time + */ + private boolean ocVersionSupportsTimeCreated(){ + /*if(mAccount != null){ + AccountManager accManager = (AccountManager) getActivity().getSystemService(Context.ACCOUNT_SERVICE); + OwnCloudVersion ocVersion = new OwnCloudVersion(accManager + .getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION)); + if(ocVersion.compareTo(new OwnCloudVersion(0x030000)) < 0) { + return true; + } + }*/ + return false; + } + + + /** + * Once the file upload has finished -> update view + * + * Being notified about the finish of an upload is necessary for the next sequence: + * 1. Upload a big file. + * 2. Force a synchronization; if it finished before the upload, the file in transfer will be included in the local database and in the file list + * of its containing folder; the the server includes it in the PROPFIND requests although it's not fully upload. + * 3. Click the file in the list to see its details. + * 4. Wait for the upload finishes; at this moment, the details view must be refreshed to enable the action buttons. + */ + private class UploadFinishReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME); + + if (!isEmpty() && accountName.equals(mAccount.name)) { + boolean uploadWasFine = intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false); + String uploadRemotePath = intent.getStringExtra(FileUploader.EXTRA_REMOTE_PATH); + boolean renamedInUpload = getFile().getRemotePath().equals(intent.getStringExtra(FileUploader.EXTRA_OLD_REMOTE_PATH)); + if (getFile().getRemotePath().equals(uploadRemotePath) || + renamedInUpload) { + if (uploadWasFine) { + setFile(mStorageManager.getFileByPath(uploadRemotePath)); + } + if (renamedInUpload) { + String newName = (new File(uploadRemotePath)).getName(); + Toast msg = Toast.makeText(getActivity().getApplicationContext(), String.format(getString(R.string.filedetails_renamed_in_upload_msg), newName), Toast.LENGTH_LONG); + msg.show(); + } + getSherlockActivity().removeStickyBroadcast(intent); // not the best place to do this; a small refactorization of BroadcastReceivers should be done + + updateFileDetails(false, false); // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server + + // Force the preview if the file is an image + if (uploadWasFine && PreviewImageFragment.canBePreviewed(getFile())) { + ((FileDisplayActivity) mContainerActivity).startImagePreview(getFile()); + } + } + } + } + } + + + public void onDismiss(EditNameDialog dialog) { + if (dialog.getResult()) { + String newFilename = dialog.getNewFilename(); + Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename); + mLastRemoteOperation = new RenameFileOperation( getFile(), + mAccount, + newFilename, + new FileDataStorageManager(mAccount, getActivity().getContentResolver())); + mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); + ((FileDisplayActivity) getActivity()).showLoadingDialog(); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { + if (operation.equals(mLastRemoteOperation)) { + if (operation instanceof RemoveFileOperation) { + onRemoveFileOperationFinish((RemoveFileOperation)operation, result); + + } else if (operation instanceof RenameFileOperation) { + onRenameFileOperationFinish((RenameFileOperation)operation, result); + + } else if (operation instanceof SynchronizeFileOperation) { + onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result); + } + } + } + + + private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) { + ((FileDisplayActivity) getActivity()).dismissLoadingDialog(); + if (result.isSuccess()) { + Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG); + msg.show(); + ((FileDisplayActivity)getActivity()).cleanSecondFragment(); + + } else { + Toast msg = Toast.makeText(getActivity(), R.string.remove_fail_msg, Toast.LENGTH_LONG); + msg.show(); + if (result.isSslRecoverableException()) { + // TODO show the SSL warning dialog + } + } + } + + private void onRenameFileOperationFinish(RenameFileOperation operation, RemoteOperationResult result) { + ((FileDisplayActivity) getActivity()).dismissLoadingDialog(); + + if (result.isSuccess()) { + updateFileDetails(((RenameFileOperation)operation).getFile(), mAccount); + mContainerActivity.onFileStateChanged(); + + } else { + if (result.getCode().equals(ResultCode.INVALID_LOCAL_FILE_NAME)) { + Toast msg = Toast.makeText(getActivity(), R.string.rename_local_fail_msg, Toast.LENGTH_LONG); + msg.show(); + // TODO throw again the new rename dialog + } else { + Toast msg = Toast.makeText(getActivity(), R.string.rename_server_fail_msg, Toast.LENGTH_LONG); + msg.show(); + if (result.isSslRecoverableException()) { + // TODO show the SSL warning dialog + } + } + } + } + + private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) { + ((FileDisplayActivity) getActivity()).dismissLoadingDialog(); + OCFile file = getFile(); + if (!result.isSuccess()) { + if (result.getCode() == ResultCode.SYNC_CONFLICT) { + Intent i = new Intent(getActivity(), ConflictsResolveActivity.class); + i.putExtra(ConflictsResolveActivity.EXTRA_FILE, file); + i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mAccount); + startActivity(i); + + } else { + Toast msg = Toast.makeText(getActivity(), R.string.sync_file_fail_msg, Toast.LENGTH_LONG); + msg.show(); + } + + if (file.isDown()) { + setButtonsForDown(); + + } else { + setButtonsForRemote(); + } + + } else { + if (operation.transferWasRequested()) { + setButtonsForTransferring(); + mContainerActivity.onFileStateChanged(); // this is not working; FileDownloader won't do NOTHING at all until this method finishes, so + // checking the service to see if the file is downloading results in FALSE + } else { + Toast msg = Toast.makeText(getActivity(), R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG); + msg.show(); + if (file.isDown()) { + setButtonsForDown(); + + } else { + setButtonsForRemote(); + } + } + } + } + + + public void listenForTransferProgress() { + if (mProgressListener != null) { + if (mContainerActivity.getFileDownloaderBinder() != null) { + mContainerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, getFile()); + } + if (mContainerActivity.getFileUploaderBinder() != null) { + mContainerActivity.getFileUploaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, getFile()); + } + } + } + + + public void leaveTransferProgress() { + if (mProgressListener != null) { + if (mContainerActivity.getFileDownloaderBinder() != null) { + mContainerActivity.getFileDownloaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, getFile()); + } + if (mContainerActivity.getFileUploaderBinder() != null) { + mContainerActivity.getFileUploaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, getFile()); + } + } + } + + + + /** + * Helper class responsible for updating the progress bar shown for file uploading or downloading + * + * @author David A. Velasco + */ + private class ProgressListener implements OnDatatransferProgressListener { + int mLastPercent = 0; + WeakReference mProgressBar = null; + + ProgressListener(ProgressBar progressBar) { + mProgressBar = new WeakReference(progressBar); + } + + @Override + public void onTransferProgress(long progressRate) { + // old method, nothing here + }; + + @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/fragment/FileFragment.java b/src/com/owncloud/android/ui/fragment/FileFragment.java new file mode 100644 index 00000000..b6c65749 --- /dev/null +++ b/src/com/owncloud/android/ui/fragment/FileFragment.java @@ -0,0 +1,102 @@ +/* ownCloud Android client application + * 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.support.v4.app.Fragment; + +import com.actionbarsherlock.app.SherlockFragment; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.FileHandler; +import com.owncloud.android.ui.activity.TransferServiceGetter; + + +/** + * Common methods for {@link Fragment}s containing {@link OCFile}s + * + * @author David A. Velasco + * + */ +public class FileFragment extends SherlockFragment { + + private OCFile mFile; + + + /** + * Creates an empty fragment. + * + * It's necessary to keep a public constructor without parameters; the system uses it when tries to reinstantiate a fragment automatically. + */ + public FileFragment() { + mFile = null; + } + + /** + * Creates an instance for a given {@OCFile}. + * + * @param file + */ + public FileFragment(OCFile file) { + mFile = file; + } + + /** + * Getter for the hold {@link OCFile} + * + * @return The {@link OCFile} hold + */ + public OCFile getFile() { + return mFile; + } + + + protected void setFile(OCFile file) { + mFile = file; + } + + /** + * Interface to implement by any Activity that includes some instance of FileFragment + * + * @author David A. Velasco + */ + public interface ContainerActivity extends TransferServiceGetter, FileHandler { + + /** + * Callback method invoked when the detail fragment wants to notice its container + * activity about a relevant state the file shown by the fragment. + * + * Added to notify to FileDisplayActivity about the need of refresh the files list. + * + * Currently called when: + * - a download is started; + * - a rename is completed; + * - a deletion is completed; + * - the 'inSync' flag is changed; + */ + public void onFileStateChanged(); + + /** + * Request the parent activity to show the details of an {@link OCFile}. + * + * @param file File to show details + */ + public void showDetails(OCFile file); + + + } + +} diff --git a/src/com/owncloud/android/ui/fragment/LandingPageFragment.java b/src/com/owncloud/android/ui/fragment/LandingPageFragment.java new file mode 100644 index 00000000..63a95fdd --- /dev/null +++ b/src/com/owncloud/android/ui/fragment/LandingPageFragment.java @@ -0,0 +1,58 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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 com.actionbarsherlock.app.SherlockFragment; +import com.owncloud.android.R; +import com.owncloud.android.ui.activity.LandingActivity; +import com.owncloud.android.ui.adapter.LandingScreenAdapter; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +/** + * Used on the Landing page to display what Components of the ownCloud there + * are. Like Files, Music, Contacts, etc. + * + * @author Lennart Rosam + * + */ +public class LandingPageFragment extends SherlockFragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.landing_page_fragment, container); + return root; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + ListView landingScreenItems = (ListView) getView().findViewById( + R.id.homeScreenList); + landingScreenItems.setAdapter(new LandingScreenAdapter(getActivity())); + landingScreenItems + .setOnItemClickListener((LandingActivity) getActivity()); + } + +} diff --git a/src/com/owncloud/android/ui/fragment/LocalFileListFragment.java b/src/com/owncloud/android/ui/fragment/LocalFileListFragment.java new file mode 100644 index 00000000..40c03a46 --- /dev/null +++ b/src/com/owncloud/android/ui/fragment/LocalFileListFragment.java @@ -0,0 +1,251 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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 java.io.File; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.ui.adapter.LocalFileListAdapter; + + +import android.app.Activity; +import android.os.Bundle; +import android.os.Environment; +import android.util.SparseBooleanArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ImageView; +import android.widget.ListView; + + +/** + * A Fragment that lists all files and folders in a given LOCAL path. + * + * @author David A. Velasco + * + */ +public class LocalFileListFragment extends ExtendedListFragment { + private static final String TAG = "LocalFileListFragment"; + + /** Reference to the Activity which this fragment is attached to. For callbacks */ + private LocalFileListFragment.ContainerActivity mContainerActivity; + + /** Directory to show */ + private File mDirectory = null; + + /** Adapter to connect the data from the directory with the View object */ + private LocalFileListAdapter mAdapter = null; + + + /** + * {@inheritDoc} + */ + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mContainerActivity = (ContainerActivity) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement " + LocalFileListFragment.ContainerActivity.class.getSimpleName()); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Log_OC.i(TAG, "onCreateView() start"); + View v = super.onCreateView(inflater, container, savedInstanceState); + getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + Log_OC.i(TAG, "onCreateView() end"); + return v; + } + + + /** + * {@inheritDoc} + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + Log_OC.i(TAG, "onActivityCreated() start"); + + super.onCreate(savedInstanceState); + mAdapter = new LocalFileListAdapter(mContainerActivity.getInitialDirectory(), getActivity()); + setListAdapter(mAdapter); + + Log_OC.i(TAG, "onActivityCreated() stop"); + } + + + /** + * Checks the file clicked over. Browses inside if it is a directory. Notifies the container activity in any case. + */ + @Override + public void onItemClick(AdapterView l, View v, int position, long id) { + File file = (File) mAdapter.getItem(position); + if (file != null) { + /// Click on a directory + if (file.isDirectory()) { + // just local updates + listDirectory(file); + // notify the click to container Activity + mContainerActivity.onDirectoryClick(file); + + } else { /// Click on a file + ImageView checkBoxV = (ImageView) v.findViewById(R.id.custom_checkbox); + if (checkBoxV != null) { + if (getListView().isItemChecked(position)) { + checkBoxV.setImageResource(android.R.drawable.checkbox_on_background); + } else { + checkBoxV.setImageResource(android.R.drawable.checkbox_off_background); + } + } + // notify the change to the container Activity + mContainerActivity.onFileClick(file); + } + + } else { + Log_OC.w(TAG, "Null object in ListAdapter!!"); + } + } + + + /** + * Call this, when the user presses the up button + */ + public void onNavigateUp() { + File parentDir = null; + if(mDirectory != null) { + parentDir = mDirectory.getParentFile(); // can be null + } + listDirectory(parentDir); + } + + + /** + * Use this to query the {@link File} object for the directory + * that is currently being displayed by this fragment + * + * @return File The currently displayed directory + */ + public File getCurrentDirectory(){ + return mDirectory; + } + + + /** + * Calls {@link LocalFileListFragment#listDirectory(File)} with a null parameter + * to refresh the current directory. + */ + public void listDirectory(){ + listDirectory(null); + } + + + /** + * Lists the given directory on the view. When the input parameter is null, + * it will either refresh the last known directory. list the root + * if there never was a directory. + * + * @param directory Directory to be listed + */ + public void listDirectory(File directory) { + + // Check input parameters for null + if(directory == null) { + if(mDirectory != null){ + directory = mDirectory; + } else { + directory = Environment.getExternalStorageDirectory(); // TODO be careful with the state of the storage; could not be available + if (directory == null) return; // no files to show + } + } + + + // if that's not a directory -> List its parent + if(!directory.isDirectory()){ + Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString()); + directory = directory.getParentFile(); + } + + mList.clearChoices(); // by now, only files in the same directory will be kept as selected + mAdapter.swapDirectory(directory); + if (mDirectory == null || !mDirectory.equals(directory)) { + mList.setSelectionFromTop(0, 0); + } + mDirectory = directory; + } + + + /** + * Returns the fule paths to the files checked by the user + * + * @return File paths to the files checked by the user. + */ + public String[] getCheckedFilePaths() { + String [] result = null; + SparseBooleanArray positions = mList.getCheckedItemPositions(); + if (positions.size() > 0) { + Log_OC.d(TAG, "Returning " + positions.size() + " selected files"); + result = new String[positions.size()]; + for (int i=0; i. + * + */ +package com.owncloud.android.ui.fragment; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.FileHandler; +import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; +import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; +import com.owncloud.android.operations.OnRemoteOperationListener; +import com.owncloud.android.operations.RemoteOperation; +import com.owncloud.android.operations.RemoveFileOperation; +import com.owncloud.android.operations.RenameFileOperation; +import com.owncloud.android.operations.SynchronizeFileOperation; +import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.ui.activity.TransferServiceGetter; +import com.owncloud.android.ui.adapter.FileListListAdapter; +import com.owncloud.android.ui.dialog.EditNameDialog; +import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener; +import com.owncloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener; +import com.owncloud.android.ui.preview.PreviewImageFragment; +import com.owncloud.android.ui.preview.PreviewMediaFragment; + + +import android.accounts.Account; +import android.app.Activity; +import android.os.Bundle; +import android.os.Handler; +import android.view.ContextMenu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; + +/** + * A Fragment that lists all files and folders in a given path. + * + * @author Bartek Przybylski + * + */ +public class OCFileListFragment extends ExtendedListFragment implements EditNameDialogListener, ConfirmationDialogFragmentListener { + + private static final String TAG = OCFileListFragment.class.getSimpleName(); + + private static final String MY_PACKAGE = OCFileListFragment.class.getPackage() != null ? OCFileListFragment.class.getPackage().getName() : "com.owncloud.android.ui.fragment"; + private static final String EXTRA_FILE = MY_PACKAGE + ".extra.FILE"; + + private OCFileListFragment.ContainerActivity mContainerActivity; + + private OCFile mFile = null; + private FileListListAdapter mAdapter; + + private Handler mHandler; + private OCFile mTargetFile; + + /** + * {@inheritDoc} + */ + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + Log_OC.e(TAG, "onAttach"); + try { + mContainerActivity = (ContainerActivity) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement " + OCFileListFragment.ContainerActivity.class.getSimpleName()); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + Log_OC.e(TAG, "onActivityCreated() start"); + mAdapter = new FileListListAdapter(getActivity(), mContainerActivity); + if (savedInstanceState != null) { + mFile = savedInstanceState.getParcelable(EXTRA_FILE); + } + setListAdapter(mAdapter); + + registerForContextMenu(getListView()); + getListView().setOnCreateContextMenuListener(this); + + mHandler = new Handler(); + + } + + /** + * Saves the current listed folder. + */ + @Override + public void onSaveInstanceState (Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(EXTRA_FILE, mFile); + } + + + /** + * Call this, when the user presses the up button + */ + public void onBrowseUp() { + OCFile parentDir = null; + + if(mFile != null){ + DataStorageManager storageManager = mContainerActivity.getStorageManager(); + parentDir = storageManager.getFileById(mFile.getParentId()); + mFile = parentDir; + } + listDirectory(parentDir); + } + + @Override + public void onItemClick(AdapterView l, View v, int position, long id) { + OCFile file = (OCFile) mAdapter.getItem(position); + if (file != null) { + if (file.isDirectory()) { + // update state and view of this fragment + listDirectory(file); + // then, notify parent activity to let it update its state and view, and other fragments + mContainerActivity.onBrowsedDownTo(file); + + } else { /// Click on a file + if (PreviewImageFragment.canBePreviewed(file)) { + // preview image - it handles the download, if needed + mContainerActivity.startImagePreview(file); + + } else if (file.isDown()) { + if (PreviewMediaFragment.canBePreviewed(file)) { + // media preview + mContainerActivity.startMediaPreview(file, 0, true); + } else { + // open with + mContainerActivity.openFile(file); + } + + } else { + // automatic download, preview on finish + mContainerActivity.startDownloadForPreview(file); + } + + } + + } else { + Log_OC.d(TAG, "Null object in ListAdapter!!"); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + MenuInflater inflater = getActivity().getMenuInflater(); + inflater.inflate(R.menu.file_actions_menu, menu); + AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; + OCFile targetFile = (OCFile) mAdapter.getItem(info.position); + List toHide = new ArrayList(); + List toDisable = new ArrayList(); + + MenuItem item = null; + if (targetFile.isDirectory()) { + // contextual menu for folders + toHide.add(R.id.action_open_file_with); + toHide.add(R.id.action_download_file); + toHide.add(R.id.action_cancel_download); + toHide.add(R.id.action_cancel_upload); + toHide.add(R.id.action_sync_file); + toHide.add(R.id.action_see_details); + if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) || + mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ) { + toDisable.add(R.id.action_rename_file); + toDisable.add(R.id.action_remove_file); + + } + + } else { + // contextual menu for regular files + + // new design: 'download' and 'open with' won't be available anymore in context menu + toHide.add(R.id.action_download_file); + toHide.add(R.id.action_open_file_with); + + if (targetFile.isDown()) { + toHide.add(R.id.action_cancel_download); + toHide.add(R.id.action_cancel_upload); + + } else { + toHide.add(R.id.action_sync_file); + } + if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) { + toHide.add(R.id.action_cancel_upload); + toDisable.add(R.id.action_rename_file); + toDisable.add(R.id.action_remove_file); + + } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) { + toHide.add(R.id.action_cancel_download); + toDisable.add(R.id.action_rename_file); + toDisable.add(R.id.action_remove_file); + + } else { + toHide.add(R.id.action_cancel_download); + toHide.add(R.id.action_cancel_upload); + } + } + + for (int i : toHide) { + item = menu.findItem(i); + if (item != null) { + item.setVisible(false); + item.setEnabled(false); + } + } + + for (int i : toDisable) { + item = menu.findItem(i); + if (item != null) { + item.setEnabled(false); + } + } + } + + + /** + * {@inhericDoc} + */ + @Override + public boolean onContextItemSelected (MenuItem item) { + AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); + mTargetFile = (OCFile) mAdapter.getItem(info.position); + switch (item.getItemId()) { + case R.id.action_rename_file: { + String fileName = mTargetFile.getFileName(); + int extensionStart = mTargetFile.isDirectory() ? -1 : fileName.lastIndexOf("."); + int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length(); + EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this); + dialog.show(getFragmentManager(), EditNameDialog.TAG); + return true; + } + case R.id.action_remove_file: { + int messageStringId = R.string.confirmation_remove_alert; + int posBtnStringId = R.string.confirmation_remove_remote; + int neuBtnStringId = -1; + if (mTargetFile.isDirectory()) { + messageStringId = R.string.confirmation_remove_folder_alert; + posBtnStringId = R.string.confirmation_remove_remote_and_local; + neuBtnStringId = R.string.confirmation_remove_folder_local; + } else if (mTargetFile.isDown()) { + posBtnStringId = R.string.confirmation_remove_remote_and_local; + neuBtnStringId = R.string.confirmation_remove_local; + } + ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance( + messageStringId, + new String[]{mTargetFile.getFileName()}, + posBtnStringId, + neuBtnStringId, + R.string.common_cancel); + confDialog.setOnConfirmationListener(this); + confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION); + return true; + } + case R.id.action_sync_file: { + Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()); + RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, false, getSherlockActivity()); + operation.execute(account, getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity()); + ((FileDisplayActivity) getSherlockActivity()).showLoadingDialog(); + return true; + } + case R.id.action_cancel_download: { + FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); + Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity()); + if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) { + downloaderBinder.cancel(account, mTargetFile); + listDirectory(); + mContainerActivity.onTransferStateChanged(mTargetFile, false, false); + } + return true; + } + case R.id.action_cancel_upload: { + FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); + Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity()); + if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) { + uploaderBinder.cancel(account, mTargetFile); + listDirectory(); + mContainerActivity.onTransferStateChanged(mTargetFile, false, false); + } + return true; + } + case R.id.action_see_details: { + ((FileFragment.ContainerActivity)getActivity()).showDetails(mTargetFile); + return true; + } + default: + return super.onContextItemSelected(item); + } + } + + + /** + * Use this to query the {@link OCFile} that is currently + * being displayed by this fragment + * @return The currently viewed OCFile + */ + public OCFile getCurrentFile(){ + return mFile; + } + + /** + * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter + */ + public void listDirectory(){ + listDirectory(null); + } + + /** + * Lists the given directory on the view. When the input parameter is null, + * it will either refresh the last known directory. list the root + * if there never was a directory. + * + * @param directory File to be listed + */ + public void listDirectory(OCFile directory) { + DataStorageManager storageManager = mContainerActivity.getStorageManager(); + if (storageManager != null) { + + // Check input parameters for null + if(directory == null){ + if(mFile != null){ + directory = mFile; + } else { + directory = storageManager.getFileByPath("/"); + if (directory == null) return; // no files, wait for sync + } + } + + + // If that's not a directory -> List its parent + if(!directory.isDirectory()){ + Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString()); + directory = storageManager.getFileById(directory.getParentId()); + } + + mAdapter.swapDirectory(directory, storageManager); + if (mFile == null || !mFile.equals(directory)) { + mList.setSelectionFromTop(0, 0); + } + mFile = directory; + + } + } + + + + /** + * Interface to implement by any Activity that includes some instance of FileListFragment + * + * @author David A. Velasco + */ + public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener, FileHandler { + + /** + * Callback method invoked when a the user browsed into a different folder through the list of files + * + * @param file + */ + public void onBrowsedDownTo(OCFile folder); + + public void startDownloadForPreview(OCFile file); + + public void startMediaPreview(OCFile file, int i, boolean b); + + public void startImagePreview(OCFile file); + + /** + * Getter for the current DataStorageManager in the container activity + */ + public DataStorageManager getStorageManager(); + + + /** + * Callback method invoked when a the 'transfer state' of a file changes. + * + * This happens when a download or upload is started or ended for a file. + * + * This method is necessary by now to update the user interface of the double-pane layout in tablets + * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)} + * won't provide the needed response before the method where this is called finishes. + * + * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO) + * + * @param file OCFile which state changed. + * @param downloading Flag signaling if the file is now downloading. + * @param uploading Flag signaling if the file is now uploading. + */ + public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading); + + } + + + @Override + public void onDismiss(EditNameDialog dialog) { + if (dialog.getResult()) { + String newFilename = dialog.getNewFilename(); + Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename); + RemoteOperation operation = new RenameFileOperation(mTargetFile, + AccountUtils.getCurrentOwnCloudAccount(getActivity()), + newFilename, + mContainerActivity.getStorageManager()); + operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity()); + ((FileDisplayActivity) getActivity()).showLoadingDialog(); + } + } + + + @Override + public void onConfirmation(String callerTag) { + if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) { + if (mContainerActivity.getStorageManager().getFileById(mTargetFile.getFileId()) != null) { + RemoteOperation operation = new RemoveFileOperation( mTargetFile, + true, + mContainerActivity.getStorageManager()); + operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity()); + + ((FileDisplayActivity) getActivity()).showLoadingDialog(); + } + } + } + + @Override + public void onNeutral(String callerTag) { + File f = null; + if (mTargetFile.isDirectory()) { + // TODO run in a secondary thread? + mContainerActivity.getStorageManager().removeDirectory(mTargetFile, false, true); + + } else if (mTargetFile.isDown() && (f = new File(mTargetFile.getStoragePath())).exists()) { + f.delete(); + mTargetFile.setStoragePath(null); + mContainerActivity.getStorageManager().saveFile(mTargetFile); + } + listDirectory(); + mContainerActivity.onTransferStateChanged(mTargetFile, false, false); + } + + @Override + public void onCancel(String callerTag) { + Log_OC.d(TAG, "REMOVAL CANCELED"); + } + + +} diff --git a/src/com/owncloud/android/ui/preview/FileDownloadFragment.java b/src/com/owncloud/android/ui/preview/FileDownloadFragment.java new file mode 100644 index 00000000..306df1f7 --- /dev/null +++ b/src/com/owncloud/android/ui/preview/FileDownloadFragment.java @@ -0,0 +1,383 @@ +/* ownCloud Android client application + * + * 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.preview; + +import java.lang.ref.WeakReference; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; +import com.owncloud.android.ui.fragment.FileFragment; + +import android.accounts.Account; +import android.app.Activity; +import android.os.Bundle; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.ProgressBar; +import android.widget.TextView; + + + +import eu.alefzero.webdav.OnDatatransferProgressListener; + +/** + * This Fragment is used to monitor the progress of a file downloading. + * + * @author David A. Velasco + */ +public class FileDownloadFragment extends FileFragment implements OnClickListener { + + public static final String EXTRA_FILE = "FILE"; + public static final String EXTRA_ACCOUNT = "ACCOUNT"; + private static final String EXTRA_ERROR = "ERROR"; + + private FileFragment.ContainerActivity mContainerActivity; + + private View mView; + private Account mAccount; + + public ProgressListener mProgressListener; + private boolean mListening; + + private static final String TAG = FileDownloadFragment.class.getSimpleName(); + + private boolean mIgnoreFirstSavedState; + private boolean mError; + + + /** + * Creates an empty details fragment. + * + * It's necessary to keep a public constructor without parameters; the system uses it when tries to reinstantiate a fragment automatically. + */ + public FileDownloadFragment() { + super(); + mAccount = null; + mProgressListener = null; + mListening = false; + mIgnoreFirstSavedState = false; + mError = false; + } + + + /** + * Creates a details fragment. + * + * When 'fileToDetail' or 'ocAccount' are null, creates a dummy layout (to use when a file wasn't tapped before). + * + * @param fileToDetail An {@link OCFile} to show in the fragment + * @param ocAccount An ownCloud account; needed to start downloads + * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStatePagerAdapter}; TODO better solution + */ + public FileDownloadFragment(OCFile fileToDetail, Account ocAccount, boolean ignoreFirstSavedState) { + super(fileToDetail); + mAccount = ocAccount; + mProgressListener = null; + mListening = false; + mIgnoreFirstSavedState = ignoreFirstSavedState; + mError = false; + } + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + + if (savedInstanceState != null) { + if (!mIgnoreFirstSavedState) { + setFile((OCFile)savedInstanceState.getParcelable(FileDownloadFragment.EXTRA_FILE)); + mAccount = savedInstanceState.getParcelable(FileDownloadFragment.EXTRA_ACCOUNT); + mError = savedInstanceState.getBoolean(FileDownloadFragment.EXTRA_ERROR); + } else { + mIgnoreFirstSavedState = false; + } + } + + View view = null; + view = inflater.inflate(R.layout.file_download_fragment, container, false); + mView = view; + + ProgressBar progressBar = (ProgressBar)mView.findViewById(R.id.progressBar); + mProgressListener = new ProgressListener(progressBar); + + ((ImageButton)mView.findViewById(R.id.cancelBtn)).setOnClickListener(this); + + if (mError) { + setButtonsForRemote(); + } else { + setButtonsForTransferring(); + } + + return view; + } + + + /** + * {@inheritDoc} + */ + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mContainerActivity = (ContainerActivity) activity; + + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement " + FileFragment.ContainerActivity.class.getSimpleName()); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (mAccount != null) { + //mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());; + } + } + + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(FileDownloadFragment.EXTRA_FILE, getFile()); + outState.putParcelable(FileDownloadFragment.EXTRA_ACCOUNT, mAccount); + outState.putBoolean(FileDownloadFragment.EXTRA_ERROR, mError); + } + + @Override + public void onStart() { + super.onStart(); + listenForTransferProgress(); + } + + @Override + public void onResume() { + super.onResume(); + } + + + @Override + public void onPause() { + super.onPause(); + } + + + @Override + public void onStop() { + super.onStop(); + leaveTransferProgress(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + + @Override + public View getView() { + if (!mListening) { + listenForTransferProgress(); + } + return super.getView() == null ? mView : super.getView(); + } + + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.cancelBtn: { + FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); + if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile())) { + downloaderBinder.cancel(mAccount, getFile()); + getActivity().finish(); // :) + /* + leaveTransferProgress(); + if (mFile.isDown()) { + setButtonsForDown(); + } else { + setButtonsForRemote(); + } + */ + } + break; + } + default: + Log_OC.e(TAG, "Incorrect view clicked!"); + } + } + + + /** + * Updates the view depending upon the state of the downloading file. + * + * @param transferring When true, the view must be updated assuming that the holded file is + * downloading, no matter what the downloaderBinder says. + */ + public void updateView(boolean transferring) { + // configure UI for depending upon local state of the file + FileDownloaderBinder downloaderBinder = (mContainerActivity == null) ? null : mContainerActivity.getFileDownloaderBinder(); + if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile()))) { + setButtonsForTransferring(); + + } else if (getFile().isDown()) { + + setButtonsForDown(); + + } else { + setButtonsForRemote(); + } + getView().invalidate(); + + } + + + /** + * Enables or disables buttons for a file being downloaded + */ + private void setButtonsForTransferring() { + getView().findViewById(R.id.cancelBtn).setVisibility(View.VISIBLE); + + // show the progress bar for the transfer + getView().findViewById(R.id.progressBar).setVisibility(View.VISIBLE); + TextView progressText = (TextView)getView().findViewById(R.id.progressText); + progressText.setText(R.string.downloader_download_in_progress_ticker); + progressText.setVisibility(View.VISIBLE); + + // hides the error icon + getView().findViewById(R.id.errorText).setVisibility(View.GONE); + getView().findViewById(R.id.error_image).setVisibility(View.GONE); + } + + + /** + * Enables or disables buttons for a file locally available + */ + private void setButtonsForDown() { + getView().findViewById(R.id.cancelBtn).setVisibility(View.GONE); + + // hides the progress bar + getView().findViewById(R.id.progressBar).setVisibility(View.GONE); + + // updates the text message + TextView progressText = (TextView)getView().findViewById(R.id.progressText); + progressText.setText(R.string.common_loading); + progressText.setVisibility(View.VISIBLE); + + // hides the error icon + getView().findViewById(R.id.errorText).setVisibility(View.GONE); + getView().findViewById(R.id.error_image).setVisibility(View.GONE); + } + + + /** + * Enables or disables buttons for a file not locally available + * + * Currently, this is only used when a download was failed + */ + private void setButtonsForRemote() { + getView().findViewById(R.id.cancelBtn).setVisibility(View.GONE); + + // hides the progress bar and message + getView().findViewById(R.id.progressBar).setVisibility(View.GONE); + getView().findViewById(R.id.progressText).setVisibility(View.GONE); + + // shows the error icon and message + getView().findViewById(R.id.errorText).setVisibility(View.VISIBLE); + getView().findViewById(R.id.error_image).setVisibility(View.VISIBLE); + } + + + public void listenForTransferProgress() { + if (mProgressListener != null && !mListening) { + if (mContainerActivity.getFileDownloaderBinder() != null) { + mContainerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, getFile()); + mListening = true; + setButtonsForTransferring(); + } + } + } + + + public void leaveTransferProgress() { + if (mProgressListener != null) { + if (mContainerActivity.getFileDownloaderBinder() != null) { + mContainerActivity.getFileDownloaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, getFile()); + mListening = false; + } + } + } + + + /** + * Helper class responsible for updating the progress bar shown for file uploading or downloading + * + * @author David A. Velasco + */ + private class ProgressListener implements OnDatatransferProgressListener { + int mLastPercent = 0; + WeakReference mProgressBar = null; + + ProgressListener(ProgressBar progressBar) { + mProgressBar = new WeakReference(progressBar); + } + + @Override + public void onTransferProgress(long progressRate) { + // old method, nothing here + }; + + @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; + } + + } + + + public void setError(boolean error) { + mError = error; + }; + + + +} diff --git a/src/com/owncloud/android/ui/preview/PreviewImageActivity.java b/src/com/owncloud/android/ui/preview/PreviewImageActivity.java new file mode 100644 index 00000000..8f12ac70 --- /dev/null +++ b/src/com/owncloud/android/ui/preview/PreviewImageActivity.java @@ -0,0 +1,467 @@ +/* ownCloud Android client application + * 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.preview; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.ViewPager; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.view.MenuItem; +import com.actionbarsherlock.view.Window; +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; +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.FileDownloader.FileDownloaderBinder; +import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.ui.dialog.LoadingDialog; +import com.owncloud.android.ui.fragment.FileFragment; + + +/** + * Holds a swiping galley where image files contained in an ownCloud directory are shown + * + * @author David A. Velasco + */ +public class PreviewImageActivity extends FileActivity implements FileFragment.ContainerActivity, ViewPager.OnPageChangeListener, OnTouchListener { + + public static final int DIALOG_SHORT_WAIT = 0; + + public static final String TAG = PreviewImageActivity.class.getSimpleName(); + + public static final String KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW"; + private static final String KEY_WAITING_FOR_BINDER = "WAITING_FOR_BINDER"; + + private static final String DIALOG_WAIT_TAG = "DIALOG_WAIT"; + + private DataStorageManager mStorageManager; + + private ViewPager mViewPager; + private PreviewImagePagerAdapter mPreviewImagePagerAdapter; + + private FileDownloaderBinder mDownloaderBinder = null; + private ServiceConnection mDownloadConnection, mUploadConnection = null; + private FileUploaderBinder mUploaderBinder = null; + + private boolean mRequestWaitingForBinder; + + private DownloadFinishReceiver mDownloadFinishReceiver; + + private boolean mFullScreen; + + private String mDownloadAddedMessage; + private String mDownloadFinishMessage; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); + setContentView(R.layout.preview_image_activity); + + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.hide(); + + mFullScreen = true; + if (savedInstanceState != null) { + mRequestWaitingForBinder = savedInstanceState.getBoolean(KEY_WAITING_FOR_BINDER); + } else { + mRequestWaitingForBinder = false; + } + + FileDownloader downloader = new FileDownloader(); + mDownloadAddedMessage = downloader.getDownloadAddedMessage(); + mDownloadFinishMessage= downloader.getDownloadFinishMessage(); + } + + private void initViewPager() { + // get parent from path + String parentPath = getFile().getRemotePath().substring(0, getFile().getRemotePath().lastIndexOf(getFile().getFileName())); + OCFile parentFolder = mStorageManager.getFileByPath(parentPath); + //OCFile parentFolder = mStorageManager.getFileById(getFile().getParentId()); + if (parentFolder == null) { + // should not be necessary + parentFolder = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR); + } + mPreviewImagePagerAdapter = new PreviewImagePagerAdapter(getSupportFragmentManager(), parentFolder, getAccount(), mStorageManager); + mViewPager = (ViewPager) findViewById(R.id.fragmentPager); + int position = mPreviewImagePagerAdapter.getFilePosition(getFile()); + position = (position >= 0) ? position : 0; + mViewPager.setAdapter(mPreviewImagePagerAdapter); + mViewPager.setOnPageChangeListener(this); + mViewPager.setCurrentItem(position); + if (position == 0 && !getFile().isDown()) { + // this is necessary because mViewPager.setCurrentItem(0) just after setting the adapter does not result in a call to #onPageSelected(0) + mRequestWaitingForBinder = true; + } + } + + + @Override + public void onStart() { + super.onStart(); + mDownloadConnection = new PreviewImageServiceConnection(); + bindService(new Intent(this, FileDownloader.class), mDownloadConnection, Context.BIND_AUTO_CREATE); + mUploadConnection = new PreviewImageServiceConnection(); + bindService(new Intent(this, FileUploader.class), mUploadConnection, Context.BIND_AUTO_CREATE); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(KEY_WAITING_FOR_BINDER, mRequestWaitingForBinder); + } + + + /** Defines callbacks for service binding, passed to bindService() */ + private class PreviewImageServiceConnection implements ServiceConnection { + + @Override + public void onServiceConnected(ComponentName component, IBinder service) { + + if (component.equals(new ComponentName(PreviewImageActivity.this, FileDownloader.class))) { + mDownloaderBinder = (FileDownloaderBinder) service; + if (mRequestWaitingForBinder) { + mRequestWaitingForBinder = false; + Log_OC.d(TAG, "Simulating reselection of current page after connection of download binder"); + onPageSelected(mViewPager.getCurrentItem()); + } + + } else if (component.equals(new ComponentName(PreviewImageActivity.this, FileUploader.class))) { + Log_OC.d(TAG, "Upload service connected"); + mUploaderBinder = (FileUploaderBinder) service; + } else { + return; + } + + } + + @Override + public void onServiceDisconnected(ComponentName component) { + if (component.equals(new ComponentName(PreviewImageActivity.this, FileDownloader.class))) { + Log_OC.d(TAG, "Download service suddenly disconnected"); + mDownloaderBinder = null; + } else if (component.equals(new ComponentName(PreviewImageActivity.this, FileUploader.class))) { + Log_OC.d(TAG, "Upload service suddenly disconnected"); + mUploaderBinder = null; + } + } + }; + + + @Override + public void onStop() { + super.onStop(); + if (mDownloadConnection != null) { + unbindService(mDownloadConnection); + mDownloadConnection = null; + } + if (mUploadConnection != null) { + unbindService(mUploadConnection); + mUploadConnection = null; + } + } + + + @Override + public void onDestroy() { + super.onDestroy(); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + boolean returnValue = false; + + switch(item.getItemId()){ + case android.R.id.home: + backToDisplayActivity(); + returnValue = true; + break; + default: + returnValue = super.onOptionsItemSelected(item); + } + + return returnValue; + } + + + @Override + protected void onResume() { + super.onResume(); + //Log.e(TAG, "ACTIVITY, ONRESUME"); + mDownloadFinishReceiver = new DownloadFinishReceiver(); + + IntentFilter filter = new IntentFilter(mDownloadFinishMessage); + filter.addAction(mDownloadAddedMessage); + registerReceiver(mDownloadFinishReceiver, filter); + } + + @Override + protected void onPostResume() { + //Log.e(TAG, "ACTIVITY, ONPOSTRESUME"); + super.onPostResume(); + } + + @Override + public void onPause() { + super.onPause(); + unregisterReceiver(mDownloadFinishReceiver); + mDownloadFinishReceiver = null; + } + + + private void backToDisplayActivity() { + finish(); + } + + /** + * Show loading dialog + */ + public void showLoadingDialog() { + // Construct dialog + LoadingDialog loading = new LoadingDialog(getResources().getString(R.string.wait_a_moment)); + FragmentManager fm = getSupportFragmentManager(); + FragmentTransaction ft = fm.beginTransaction(); + loading.show(ft, DIALOG_WAIT_TAG); + + } + + /** + * Dismiss loading dialog + */ + public void dismissLoadingDialog(){ + Fragment frag = getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG); + if (frag != null) { + LoadingDialog loading = (LoadingDialog) frag; + loading.dismiss(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onFileStateChanged() { + // nothing to do here! + } + + + /** + * {@inheritDoc} + */ + @Override + public FileDownloaderBinder getFileDownloaderBinder() { + return mDownloaderBinder; + } + + + @Override + public FileUploaderBinder getFileUploaderBinder() { + return mUploaderBinder; + } + + + @Override + 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_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this)); + startActivity(showDetailsIntent); + int pos = mPreviewImagePagerAdapter.getFilePosition(file); + file = mPreviewImagePagerAdapter.getFileAt(pos); + + } + + + private void requestForDownload(OCFile file) { + if (mDownloaderBinder == null) { + Log_OC.d(TAG, "requestForDownload called without binder to download service"); + + } 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); + startService(i); + } + } + + /** + * This method will be invoked when a new page becomes selected. Animation is not necessarily complete. + * + * @param Position Position index of the new selected page + */ + @Override + public void onPageSelected(int position) { + if (mDownloaderBinder == null) { + mRequestWaitingForBinder = true; + + } else { + OCFile currentFile = mPreviewImagePagerAdapter.getFileAt(position); + getSupportActionBar().setTitle(currentFile.getFileName()); + if (!currentFile.isDown()) { + if (!mPreviewImagePagerAdapter.pendingErrorAt(position)) { + requestForDownload(currentFile); + } + } + } + } + + /** + * Called when the scroll state changes. Useful for discovering when the user begins dragging, + * when the pager is automatically settling to the current page. when it is fully stopped/idle. + * + * @param State The new scroll state (SCROLL_STATE_IDLE, _DRAGGING, _SETTLING + */ + @Override + public void onPageScrollStateChanged(int state) { + } + + /** + * This method will be invoked when the current page is scrolled, either as part of a programmatically + * initiated smooth scroll or a user initiated touch scroll. + * + * @param position Position index of the first page currently being displayed. + * Page position+1 will be visible if positionOffset is nonzero. + * + * @param positionOffset Value from [0, 1) indicating the offset from the page at position. + * @param positionOffsetPixels Value in pixels indicating the offset from position. + */ + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } + + + /** + * Class waiting for broadcast events from the {@link FielDownloader} service. + * + * Updates the UI when a download is started or finished, provided that it is relevant for the + * folder displayed in the gallery. + */ + private class DownloadFinishReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME); + String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); + if (getAccount().name.equals(accountName) && + downloadedRemotePath != null) { + + OCFile file = mStorageManager.getFileByPath(downloadedRemotePath); + int position = mPreviewImagePagerAdapter.getFilePosition(file); + boolean downloadWasFine = intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false); + //boolean isOffscreen = Math.abs((mViewPager.getCurrentItem() - position)) <= mViewPager.getOffscreenPageLimit(); + + if (position >= 0 && intent.getAction().equals(mDownloadFinishMessage)) { + if (downloadWasFine) { + mPreviewImagePagerAdapter.updateFile(position, file); + + } else { + mPreviewImagePagerAdapter.updateWithDownloadError(position); + } + mPreviewImagePagerAdapter.notifyDataSetChanged(); // will trigger the creation of new fragments + + } else { + Log_OC.d(TAG, "Download finished, but the fragment is offscreen"); + } + + } + removeStickyBroadcast(intent); + } + + } + + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + toggleFullScreen(); + } + return true; + } + + + private void toggleFullScreen() { + ActionBar actionBar = getSupportActionBar(); + if (mFullScreen) { + actionBar.show(); + + } else { + actionBar.hide(); + + } + mFullScreen = !mFullScreen; + } + + @Override + protected void onAccountSet(boolean stateWasRecovered) { + if (getAccount() != null) { + OCFile file = getFile(); + /// Validate handled file (first image to preview) + if (file == null) { + throw new IllegalStateException("Instanced with a NULL OCFile"); + } + if (!file.isImage()) { + throw new IllegalArgumentException("Non-image file passed as argument"); + } + mStorageManager = new FileDataStorageManager(getAccount(), getContentResolver()); + + // Update file according to DB file, if it is possible + if (file.getFileId() > DataStorageManager.ROOT_PARENT_ID) + file = mStorageManager.getFileById(file.getFileId()); + + if (file != null) { + /// Refresh the activity according to the Account and OCFile set + setFile(file); // reset after getting it fresh from mStorageManager + getSupportActionBar().setTitle(getFile().getFileName()); + //if (!stateWasRecovered) { + initViewPager(); + //} + + } else { + // handled file not in the current Account + finish(); + } + + } else { + Log_OC.wtf(TAG, "onAccountChanged was called with NULL account associated!"); + } + } + + +} diff --git a/src/com/owncloud/android/ui/preview/PreviewImageFragment.java b/src/com/owncloud/android/ui/preview/PreviewImageFragment.java new file mode 100644 index 00000000..9ef4db4b --- /dev/null +++ b/src/com/owncloud/android/ui/preview/PreviewImageFragment.java @@ -0,0 +1,631 @@ +/* ownCloud Android client application + * 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.preview; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + + +import android.accounts.Account; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapFactory.Options; +import android.graphics.Point; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnTouchListener; +import android.view.ViewGroup; +import android.webkit.MimeTypeMap; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.operations.OnRemoteOperationListener; +import com.owncloud.android.operations.RemoteOperation; +import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.operations.RemoveFileOperation; +import com.owncloud.android.ui.fragment.ConfirmationDialogFragment; +import com.owncloud.android.ui.fragment.FileFragment; + +import eu.alefzero.webdav.WebdavUtils; + + +/** + * This fragment shows a preview of a downloaded image. + * + * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link Account} values will produce an {@link IllegalStateException}. + * + * If the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on instantiation too. + * + * @author David A. Velasco + */ +public class PreviewImageFragment extends FileFragment implements OnRemoteOperationListener, + ConfirmationDialogFragment.ConfirmationDialogFragmentListener { + public static final String EXTRA_FILE = "FILE"; + public static final String EXTRA_ACCOUNT = "ACCOUNT"; + + private View mView; + private Account mAccount; + private FileDataStorageManager mStorageManager; + private ImageView mImageView; + private TextView mMessageView; + private ProgressBar mProgressWheel; + + public Bitmap mBitmap = null; + + private Handler mHandler; + private RemoteOperation mLastRemoteOperation; + + private static final String TAG = PreviewImageFragment.class.getSimpleName(); + + private boolean mIgnoreFirstSavedState; + + + /** + * Creates a fragment to preview an image. + * + * When 'imageFile' or 'ocAccount' are null + * + * @param imageFile An {@link OCFile} to preview as an image in the fragment + * @param ocAccount An ownCloud account; needed to start downloads + * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStatePagerAdapter}; TODO better solution + */ + public PreviewImageFragment(OCFile fileToDetail, Account ocAccount, boolean ignoreFirstSavedState) { + super(fileToDetail); + mAccount = ocAccount; + mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment + mIgnoreFirstSavedState = ignoreFirstSavedState; + } + + + /** + * Creates an empty fragment for image previews. + * + * MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically (for instance, when the device is turned a aside). + * + * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction + */ + public PreviewImageFragment() { + super(); + mAccount = null; + mStorageManager = null; + mIgnoreFirstSavedState = false; + } + + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mHandler = new Handler(); + setHasOptionsMenu(true); + } + + + /** + * {@inheritDoc} + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + mView = inflater.inflate(R.layout.preview_image_fragment, container, false); + mImageView = (ImageView)mView.findViewById(R.id.image); + mImageView.setVisibility(View.GONE); + mView.setOnTouchListener((OnTouchListener)getActivity()); // WATCH OUT THAT CAST + mMessageView = (TextView)mView.findViewById(R.id.message); + mMessageView.setVisibility(View.GONE); + mProgressWheel = (ProgressBar)mView.findViewById(R.id.progressWheel); + mProgressWheel.setVisibility(View.VISIBLE); + return mView; + } + + + /** + * {@inheritDoc} + */ + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + if (!(activity instanceof FileFragment.ContainerActivity)) + throw new ClassCastException(activity.toString() + " must implement " + FileFragment.ContainerActivity.class.getSimpleName()); + } + + + /** + * {@inheritDoc} + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver()); + if (savedInstanceState != null) { + if (!mIgnoreFirstSavedState) { + setFile((OCFile)savedInstanceState.getParcelable(PreviewImageFragment.EXTRA_FILE)); + mAccount = savedInstanceState.getParcelable(PreviewImageFragment.EXTRA_ACCOUNT); + } else { + mIgnoreFirstSavedState = false; + } + } + if (getFile() == null) { + throw new IllegalStateException("Instanced with a NULL OCFile"); + } + if (mAccount == null) { + throw new IllegalStateException("Instanced with a NULL ownCloud Account"); + } + if (!getFile().isDown()) { + throw new IllegalStateException("There is no local file to preview"); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(PreviewImageFragment.EXTRA_FILE, getFile()); + outState.putParcelable(PreviewImageFragment.EXTRA_ACCOUNT, mAccount); + } + + + @Override + public void onStart() { + super.onStart(); + if (getFile() != null) { + BitmapLoader bl = new BitmapLoader(mImageView, mMessageView, mProgressWheel); + bl.execute(new String[]{getFile().getStoragePath()}); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + + inflater.inflate(R.menu.file_actions_menu, menu); + List toHide = new ArrayList(); + + MenuItem item = null; + toHide.add(R.id.action_cancel_download); + toHide.add(R.id.action_cancel_upload); + toHide.add(R.id.action_download_file); + toHide.add(R.id.action_rename_file); // by now + + for (int i : toHide) { + item = menu.findItem(i); + if (item != null) { + item.setVisible(false); + item.setEnabled(false); + } + } + + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_open_file_with: { + openFile(); + return true; + } + case R.id.action_remove_file: { + removeFile(); + return true; + } + case R.id.action_see_details: { + seeDetails(); + return true; + } + + default: + return false; + } + } + + + private void seeDetails() { + ((FileFragment.ContainerActivity)getActivity()).showDetails(getFile()); + } + + + @Override + public void onResume() { + super.onResume(); + } + + + @Override + public void onPause() { + super.onPause(); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + if (mBitmap != null) { + mBitmap.recycle(); + } + } + + + /** + * Opens the previewed image with an external application. + * + * TODO - improve this; instead of prioritize the actions available for the MIME type in the server, + * we should get a list of available apps for MIME tpye in the server and join it with the list of + * available apps for the MIME type known from the file extension, to let the user choose + */ + private void openFile() { + OCFile file = getFile(); + String storagePath = file.getStoragePath(); + String encodedStoragePath = WebdavUtils.encodePath(storagePath); + try { + Intent i = new Intent(Intent.ACTION_VIEW); + i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), file.getMimetype()); + i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + startActivity(i); + + } catch (Throwable t) { + Log_OC.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + file.getMimetype()); + boolean toastIt = true; + String mimeType = ""; + try { + Intent i = new Intent(Intent.ACTION_VIEW); + mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1)); + if (mimeType == null || !mimeType.equals(file.getMimetype())) { + if (mimeType != null) { + i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType); + } else { + // desperate try + i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*-/*"); + } + i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + startActivity(i); + toastIt = false; + } + + } catch (IndexOutOfBoundsException e) { + Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath); + + } catch (ActivityNotFoundException e) { + Log_OC.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension"); + + } catch (Throwable th) { + Log_OC.e(TAG, "Unexpected problem when opening: " + storagePath, th); + + } finally { + if (toastIt) { + Toast.makeText(getActivity(), "There is no application to handle file " + file.getFileName(), Toast.LENGTH_SHORT).show(); + } + } + + } + finish(); + } + + + /** + * Starts a the removal of the previewed file. + * + * Shows a confirmation dialog. The action continues in {@link #onConfirmation(String)} , {@link #onNeutral(String)} or {@link #onCancel(String)}, + * depending upon the user selection in the dialog. + */ + private void removeFile() { + ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance( + R.string.confirmation_remove_alert, + new String[]{getFile().getFileName()}, + R.string.confirmation_remove_remote_and_local, + R.string.confirmation_remove_local, + R.string.common_cancel); + confDialog.setOnConfirmationListener(this); + confDialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION); + } + + + /** + * Performs the removal of the previewed file, both locally and in the server. + */ + @Override + public void onConfirmation(String callerTag) { + if (mStorageManager.getFileById(getFile().getFileId()) != null) { // check that the file is still there; + mLastRemoteOperation = new RemoveFileOperation( getFile(), // TODO we need to review the interface with RemoteOperations, and use OCFile IDs instead of OCFile objects as parameters + true, + mStorageManager); + mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); + + ((PreviewImageActivity) getActivity()).showLoadingDialog(); + } + } + + + /** + * Removes the file from local storage + */ + @Override + public void onNeutral(String callerTag) { + // TODO this code should be made in a secondary thread, + OCFile file = getFile(); + if (file.isDown()) { // checks it is still there + File f = new File(file.getStoragePath()); + f.delete(); + file.setStoragePath(null); + mStorageManager.saveFile(file); + finish(); + } + } + + /** + * User cancelled the removal action. + */ + @Override + public void onCancel(String callerTag) { + // nothing to do here + } + + + private class BitmapLoader extends AsyncTask { + + /** + * Weak reference to the target {@link ImageView} where the bitmap will be loaded into. + * + * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes. + */ + private final WeakReference mImageViewRef; + + /** + * Weak reference to the target {@link TextView} where error messages will be written. + * + * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes. + */ + private final WeakReference mMessageViewRef; + + + /** + * Weak reference to the target {@link Progressbar} shown while the load is in progress. + * + * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes. + */ + private final WeakReference mProgressWheelRef; + + + /** + * Error message to show when a load fails + */ + private int mErrorMessageId; + + + /** + * Constructor. + * + * @param imageView Target {@link ImageView} where the bitmap will be loaded into. + */ + public BitmapLoader(ImageView imageView, TextView messageView, ProgressBar progressWheel) { + mImageViewRef = new WeakReference(imageView); + mMessageViewRef = new WeakReference(messageView); + mProgressWheelRef = new WeakReference(progressWheel); + } + + + @SuppressWarnings("deprecation") + @SuppressLint({ "NewApi", "NewApi", "NewApi" }) // to avoid Lint errors since Android SDK r20 + @Override + protected Bitmap doInBackground(String... params) { + Bitmap result = null; + if (params.length != 1) return result; + String storagePath = params[0]; + try { + // set desired options that will affect the size of the bitmap + BitmapFactory.Options options = new Options(); + options.inScaled = true; + options.inPurgeable = true; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + options.inPreferQualityOverSpeed = false; + } + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { + options.inMutable = false; + } + // make a false load of the bitmap - just to be able to read outWidth, outHeight and outMimeType + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(storagePath, options); + + int width = options.outWidth; + int height = options.outHeight; + int scale = 1; + + Display display = getActivity().getWindowManager().getDefaultDisplay(); + Point size = new Point(); + int screenWidth; + int screenHeight; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR2) { + display.getSize(size); + screenWidth = size.x; + screenHeight = size.y; + } else { + screenWidth = display.getWidth(); + screenHeight = display.getHeight(); + } + + if (width > screenWidth) { + // second try to scale down the image , this time depending upon the screen size + scale = (int) Math.floor((float)width / screenWidth); + } + if (height > screenHeight) { + scale = Math.max(scale, (int) Math.floor((float)height / screenHeight)); + } + options.inSampleSize = scale; + + // really load the bitmap + options.inJustDecodeBounds = false; // the next decodeFile call will be real + result = BitmapFactory.decodeFile(storagePath, options); + //Log_OC.d(TAG, "Image loaded - width: " + options.outWidth + ", loaded height: " + options.outHeight); + + if (result == null) { + mErrorMessageId = R.string.preview_image_error_unknown_format; + Log_OC.e(TAG, "File could not be loaded as a bitmap: " + storagePath); + } + + } catch (OutOfMemoryError e) { + mErrorMessageId = R.string.preview_image_error_unknown_format; + Log_OC.e(TAG, "Out of memory occured for file " + storagePath, e); + + } catch (NoSuchFieldError e) { + mErrorMessageId = R.string.common_error_unknown; + Log_OC.e(TAG, "Error from access to unexisting field despite protection; file " + storagePath, e); + + } catch (Throwable t) { + mErrorMessageId = R.string.common_error_unknown; + Log_OC.e(TAG, "Unexpected error loading " + getFile().getStoragePath(), t); + + } + return result; + } + + @Override + protected void onPostExecute(Bitmap result) { + hideProgressWheel(); + if (result != null) { + showLoadedImage(result); + } else { + showErrorMessage(); + } + } + + private void showLoadedImage(Bitmap result) { + if (mImageViewRef != null) { + final ImageView imageView = mImageViewRef.get(); + if (imageView != null) { + imageView.setImageBitmap(result); + imageView.setVisibility(View.VISIBLE); + mBitmap = result; + } // else , silently finish, the fragment was destroyed + } + if (mMessageViewRef != null) { + final TextView messageView = mMessageViewRef.get(); + if (messageView != null) { + messageView.setVisibility(View.GONE); + } // else , silently finish, the fragment was destroyed + } + } + + private void showErrorMessage() { + if (mImageViewRef != null) { + final ImageView imageView = mImageViewRef.get(); + if (imageView != null) { + // shows the default error icon + imageView.setVisibility(View.VISIBLE); + } // else , silently finish, the fragment was destroyed + } + if (mMessageViewRef != null) { + final TextView messageView = mMessageViewRef.get(); + if (messageView != null) { + messageView.setText(mErrorMessageId); + messageView.setVisibility(View.VISIBLE); + } // else , silently finish, the fragment was destroyed + } + } + + private void hideProgressWheel() { + if (mProgressWheelRef != null) { + final ProgressBar progressWheel = mProgressWheelRef.get(); + if (progressWheel != null) { + progressWheel.setVisibility(View.GONE); + } + } + } + + } + + /** + * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewImageFragment} to be previewed. + * + * @param file File to test if can be previewed. + * @return 'True' if the file can be handled by the fragment. + */ + public static boolean canBePreviewed(OCFile file) { + return (file != null && file.isImage()); + } + + + /** + * {@inheritDoc} + */ + @Override + public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { + if (operation.equals(mLastRemoteOperation) && operation instanceof RemoveFileOperation) { + onRemoveFileOperationFinish((RemoveFileOperation)operation, result); + } + } + + private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) { + ((PreviewImageActivity) getActivity()).dismissLoadingDialog(); + + if (result.isSuccess()) { + Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG); + msg.show(); + finish(); + + } else { + Toast msg = Toast.makeText(getActivity(), R.string.remove_fail_msg, Toast.LENGTH_LONG); + msg.show(); + if (result.isSslRecoverableException()) { + // TODO show the SSL warning dialog + } + } + } + + /** + * Finishes the preview + */ + private void finish() { + Activity container = getActivity(); + container.finish(); + } + + +} diff --git a/src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java b/src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java new file mode 100644 index 00000000..d17d35f7 --- /dev/null +++ b/src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java @@ -0,0 +1,333 @@ +/* ownCloud Android client application + * 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.preview; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Vector; + +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.ui.fragment.FileFragment; + +import android.accounts.Account; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.view.ViewGroup; + + +/** + * Adapter class that provides Fragment instances + * + * @author David A. Velasco + */ +//public class PreviewImagePagerAdapter extends PagerAdapter { +public class PreviewImagePagerAdapter extends FragmentStatePagerAdapter { + + private Vector mImageFiles; + private Account mAccount; + private Set mObsoleteFragments; + private Set mObsoletePositions; + private Set mDownloadErrors; + private DataStorageManager mStorageManager; + + private Map mCachedFragments; + + /** + * Constructor. + * + * @param fragmentManager {@link FragmentManager} instance that will handle the {@link Fragment}s provided by the adapter. + * @param parentFolder Folder where images will be searched for. + * @param storageManager Bridge to database. + */ + public PreviewImagePagerAdapter(FragmentManager fragmentManager, OCFile parentFolder, Account account, DataStorageManager storageManager) { + super(fragmentManager); + + if (fragmentManager == null) { + throw new IllegalArgumentException("NULL FragmentManager instance"); + } + if (parentFolder == null) { + throw new IllegalArgumentException("NULL parent folder"); + } + if (storageManager == null) { + throw new IllegalArgumentException("NULL storage manager"); + } + + mAccount = account; + mStorageManager = storageManager; + mImageFiles = mStorageManager.getDirectoryImages(parentFolder); + mObsoleteFragments = new HashSet(); + mObsoletePositions = new HashSet(); + mDownloadErrors = new HashSet(); + //mFragmentManager = fragmentManager; + mCachedFragments = new HashMap(); + } + + + /** + * Returns the image files handled by the adapter. + * + * @return A vector with the image files handled by the adapter. + */ + protected OCFile getFileAt(int position) { + return mImageFiles.get(position); + } + + + public Fragment getItem(int i) { + OCFile file = mImageFiles.get(i); + Fragment fragment = null; + if (file.isDown()) { + fragment = new PreviewImageFragment(file, mAccount, mObsoletePositions.contains(Integer.valueOf(i))); + + } else if (mDownloadErrors.contains(Integer.valueOf(i))) { + fragment = new FileDownloadFragment(file, mAccount, true); + ((FileDownloadFragment)fragment).setError(true); + mDownloadErrors.remove(Integer.valueOf(i)); + + } else { + fragment = new FileDownloadFragment(file, mAccount, mObsoletePositions.contains(Integer.valueOf(i))); + } + mObsoletePositions.remove(Integer.valueOf(i)); + return fragment; + } + + public int getFilePosition(OCFile file) { + return mImageFiles.indexOf(file); + } + + @Override + public int getCount() { + return mImageFiles.size(); + } + + @Override + public CharSequence getPageTitle(int position) { + return mImageFiles.get(position).getFileName(); + } + + + public void updateFile(int position, OCFile file) { + FileFragment fragmentToUpdate = mCachedFragments.get(Integer.valueOf(position)); + if (fragmentToUpdate != null) { + mObsoleteFragments.add(fragmentToUpdate); + } + mObsoletePositions.add(Integer.valueOf(position)); + mImageFiles.set(position, file); + } + + + public void updateWithDownloadError(int position) { + FileFragment fragmentToUpdate = mCachedFragments.get(Integer.valueOf(position)); + if (fragmentToUpdate != null) { + mObsoleteFragments.add(fragmentToUpdate); + } + mDownloadErrors.add(Integer.valueOf(position)); + } + + public void clearErrorAt(int position) { + FileFragment fragmentToUpdate = mCachedFragments.get(Integer.valueOf(position)); + if (fragmentToUpdate != null) { + mObsoleteFragments.add(fragmentToUpdate); + } + mDownloadErrors.remove(Integer.valueOf(position)); + } + + + @Override + public int getItemPosition(Object object) { + if (mObsoleteFragments.contains(object)) { + mObsoleteFragments.remove(object); + return POSITION_NONE; + } + return super.getItemPosition(object); + } + + + @Override + public Object instantiateItem(ViewGroup container, int position) { + Object fragment = super.instantiateItem(container, position); + mCachedFragments.put(Integer.valueOf(position), (FileFragment)fragment); + return fragment; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + mCachedFragments.remove(Integer.valueOf(position)); + super.destroyItem(container, position, object); + } + + + public boolean pendingErrorAt(int position) { + return mDownloadErrors.contains(Integer.valueOf(position)); + } + + /* -* + * Called when a change in the shown pages is going to start being made. + * + * @param container The containing View which is displaying this adapter's page views. + *- / + @Override + public void startUpdate(ViewGroup container) { + Log.e(TAG, "** startUpdate"); + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + Log.e(TAG, "** instantiateItem " + position); + + if (mFragments.size() > position) { + Fragment fragment = mFragments.get(position); + if (fragment != null) { + Log.e(TAG, "** \t returning cached item"); + return fragment; + } + } + + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + + Fragment fragment = getItem(position); + if (mSavedState.size() > position) { + Fragment.SavedState savedState = mSavedState.get(position); + if (savedState != null) { + // TODO WATCH OUT: + // * The Fragment must currently be attached to the FragmentManager. + // * A new Fragment created using this saved state must be the same class type as the Fragment it was created from. + // * The saved state can not contain dependencies on other fragments -- that is it can't use putFragment(Bundle, String, Fragment) + // to store a fragment reference + fragment.setInitialSavedState(savedState); + } + } + while (mFragments.size() <= position) { + mFragments.add(null); + } + fragment.setMenuVisibility(false); + mFragments.set(position, fragment); + //Log.e(TAG, "** \t adding fragment at position " + position + ", containerId " + container.getId()); + mCurTransaction.add(container.getId(), fragment); + + return fragment; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + Log.e(TAG, "** destroyItem " + position); + Fragment fragment = (Fragment)object; + + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + Log.e(TAG, "** \t removing fragment at position " + position); + while (mSavedState.size() <= position) { + mSavedState.add(null); + } + mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment)); + mFragments.set(position, null); + + mCurTransaction.remove(fragment); + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment)object; + if (fragment != mCurrentPrimaryItem) { + if (mCurrentPrimaryItem != null) { + mCurrentPrimaryItem.setMenuVisibility(false); + } + if (fragment != null) { + fragment.setMenuVisibility(true); + } + mCurrentPrimaryItem = fragment; + } + } + + @Override + public void finishUpdate(ViewGroup container) { + Log.e(TAG, "** finishUpdate (start)"); + if (mCurTransaction != null) { + mCurTransaction.commitAllowingStateLoss(); + mCurTransaction = null; + mFragmentManager.executePendingTransactions(); + } + Log.e(TAG, "** finishUpdate (end)"); + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return ((Fragment)object).getView() == view; + } + + @Override + public Parcelable saveState() { + Bundle state = null; + if (mSavedState.size() > 0) { + state = new Bundle(); + Fragment.SavedState[] savedStates = new Fragment.SavedState[mSavedState.size()]; + mSavedState.toArray(savedStates); + state.putParcelableArray("states", savedStates); + } + for (int i=0; i keys = bundle.keySet(); + for (String key: keys) { + if (key.startsWith("f")) { + int index = Integer.parseInt(key.substring(1)); + Fragment f = mFragmentManager.getFragment(bundle, key); + if (f != null) { + while (mFragments.size() <= index) { + mFragments.add(null); + } + f.setMenuVisibility(false); + mFragments.set(index, f); + } else { + Log.w(TAG, "Bad fragment at key " + key); + } + } + } + } + } + */ +} diff --git a/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java b/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java new file mode 100644 index 00000000..815dbbde --- /dev/null +++ b/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java @@ -0,0 +1,769 @@ +/* ownCloud Android client application + * 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.preview; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import android.accounts.Account; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.res.Configuration; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnCompletionListener; +import android.media.MediaPlayer.OnErrorListener; +import android.media.MediaPlayer.OnPreparedListener; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; +import android.view.ViewGroup; +import android.webkit.MimeTypeMap; +import android.widget.ImageView; +import android.widget.Toast; +import android.widget.VideoView; + +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.media.MediaControlView; +import com.owncloud.android.media.MediaService; +import com.owncloud.android.media.MediaServiceBinder; +import com.owncloud.android.operations.OnRemoteOperationListener; +import com.owncloud.android.operations.RemoteOperation; +import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.operations.RemoveFileOperation; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.ui.fragment.ConfirmationDialogFragment; +import com.owncloud.android.ui.fragment.FileFragment; + +import eu.alefzero.webdav.WebdavUtils; + +/** + * This fragment shows a preview of a downloaded media file (audio or video). + * + * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link Account} values will produce an {@link IllegalStateException}. + * + * By now, if the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on instantiation too. + * + * @author David A. Velasco + */ +public class PreviewMediaFragment extends FileFragment implements + OnTouchListener, + ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener { + + public static final String EXTRA_FILE = "FILE"; + public static final String EXTRA_ACCOUNT = "ACCOUNT"; + private static final String EXTRA_PLAY_POSITION = "PLAY_POSITION"; + private static final String EXTRA_PLAYING = "PLAYING"; + + private View mView; + private Account mAccount; + private FileDataStorageManager mStorageManager; + private ImageView mImagePreview; + private VideoView mVideoPreview; + private int mSavedPlaybackPosition; + + private Handler mHandler; + private RemoteOperation mLastRemoteOperation; + + private MediaServiceBinder mMediaServiceBinder = null; + private MediaControlView mMediaController = null; + private MediaServiceConnection mMediaServiceConnection = null; + private VideoHelper mVideoHelper; + private boolean mAutoplay; + public boolean mPrepared; + + private static final String TAG = PreviewMediaFragment.class.getSimpleName(); + + + /** + * Creates a fragment to preview a file. + * + * When 'fileToDetail' or 'ocAccount' are null + * + * @param fileToDetail An {@link OCFile} to preview in the fragment + * @param ocAccount An ownCloud account; needed to start downloads + */ + public PreviewMediaFragment(OCFile fileToDetail, Account ocAccount, int startPlaybackPosition, boolean autoplay) { + super(fileToDetail); + mAccount = ocAccount; + mSavedPlaybackPosition = startPlaybackPosition; + mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment + mAutoplay = autoplay; + } + + + /** + * Creates an empty fragment for previews. + * + * MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically (for instance, when the device is turned a aside). + * + * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction + */ + public PreviewMediaFragment() { + super(); + mAccount = null; + mSavedPlaybackPosition = 0; + mStorageManager = null; + mAutoplay = true; + } + + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mHandler = new Handler(); + setHasOptionsMenu(true); + } + + + /** + * {@inheritDoc} + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + Log_OC.e(TAG, "onCreateView"); + + + mView = inflater.inflate(R.layout.file_preview, container, false); + + mImagePreview = (ImageView)mView.findViewById(R.id.image_preview); + mVideoPreview = (VideoView)mView.findViewById(R.id.video_preview); + mVideoPreview.setOnTouchListener(this); + + mMediaController = (MediaControlView)mView.findViewById(R.id.media_controller); + + return mView; + } + + + /** + * {@inheritDoc} + */ + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + Log_OC.e(TAG, "onAttach"); + + if (!(activity instanceof FileFragment.ContainerActivity)) + throw new ClassCastException(activity.toString() + " must implement " + FileFragment.ContainerActivity.class.getSimpleName()); + } + + + /** + * {@inheritDoc} + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + Log_OC.e(TAG, "onActivityCreated"); + + mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver()); + if (savedInstanceState != null) { + setFile((OCFile)savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_FILE)); + mAccount = savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_ACCOUNT); + mSavedPlaybackPosition = savedInstanceState.getInt(PreviewMediaFragment.EXTRA_PLAY_POSITION); + mAutoplay = savedInstanceState.getBoolean(PreviewMediaFragment.EXTRA_PLAYING); + + } + OCFile file = getFile(); + if (file == null) { + throw new IllegalStateException("Instanced with a NULL OCFile"); + } + if (mAccount == null) { + throw new IllegalStateException("Instanced with a NULL ownCloud Account"); + } + if (!file.isDown()) { + throw new IllegalStateException("There is no local file to preview"); + } + if (file.isVideo()) { + mVideoPreview.setVisibility(View.VISIBLE); + mImagePreview.setVisibility(View.GONE); + prepareVideo(); + + } else { + mVideoPreview.setVisibility(View.GONE); + mImagePreview.setVisibility(View.VISIBLE); + } + + } + + + /** + * {@inheritDoc} + */ + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + Log_OC.e(TAG, "onSaveInstanceState"); + + outState.putParcelable(PreviewMediaFragment.EXTRA_FILE, getFile()); + outState.putParcelable(PreviewMediaFragment.EXTRA_ACCOUNT, mAccount); + + if (getFile().isVideo()) { + mSavedPlaybackPosition = mVideoPreview.getCurrentPosition(); + mAutoplay = mVideoPreview.isPlaying(); + outState.putInt(PreviewMediaFragment.EXTRA_PLAY_POSITION , mSavedPlaybackPosition); + outState.putBoolean(PreviewMediaFragment.EXTRA_PLAYING , mAutoplay); + } else { + outState.putInt(PreviewMediaFragment.EXTRA_PLAY_POSITION , mMediaServiceBinder.getCurrentPosition()); + outState.putBoolean(PreviewMediaFragment.EXTRA_PLAYING , mMediaServiceBinder.isPlaying()); + } + } + + + @Override + public void onStart() { + super.onStart(); + Log_OC.e(TAG, "onStart"); + + OCFile file = getFile(); + if (file != null) { + if (file.isAudio()) { + bindMediaService(); + + } else if (file.isVideo()) { + stopAudio(); + playVideo(); + } + } + } + + + private void stopAudio() { + Intent i = new Intent(getSherlockActivity(), MediaService.class); + i.setAction(MediaService.ACTION_STOP_ALL); + getSherlockActivity().startService(i); + } + + + /** + * {@inheritDoc} + */ + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + + inflater.inflate(R.menu.file_actions_menu, menu); + List toHide = new ArrayList(); + + MenuItem item = null; + toHide.add(R.id.action_cancel_download); + toHide.add(R.id.action_cancel_upload); + toHide.add(R.id.action_download_file); + toHide.add(R.id.action_sync_file); + toHide.add(R.id.action_rename_file); // by now + + for (int i : toHide) { + item = menu.findItem(i); + if (item != null) { + item.setVisible(false); + item.setEnabled(false); + } + } + + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_open_file_with: { + openFile(); + return true; + } + case R.id.action_remove_file: { + removeFile(); + return true; + } + case R.id.action_see_details: { + seeDetails(); + return true; + } + + default: + return false; + } + } + + + private void seeDetails() { + stopPreview(false); + ((FileFragment.ContainerActivity)getActivity()).showDetails(getFile()); + } + + + private void prepareVideo() { + // create helper to get more control on the playback + mVideoHelper = new VideoHelper(); + mVideoPreview.setOnPreparedListener(mVideoHelper); + mVideoPreview.setOnCompletionListener(mVideoHelper); + mVideoPreview.setOnErrorListener(mVideoHelper); + } + + private void playVideo() { + // create and prepare control panel for the user + mMediaController.setMediaPlayer(mVideoPreview); + + // load the video file in the video player ; when done, VideoHelper#onPrepared() will be called + mVideoPreview.setVideoPath(getFile().getStoragePath()); + } + + + private class VideoHelper implements OnCompletionListener, OnPreparedListener, OnErrorListener { + + /** + * Called when the file is ready to be played. + * + * Just starts the playback. + * + * @param mp {@link MediaPlayer} instance performing the playback. + */ + @Override + public void onPrepared(MediaPlayer vp) { + Log_OC.e(TAG, "onPrepared"); + mVideoPreview.seekTo(mSavedPlaybackPosition); + if (mAutoplay) { + mVideoPreview.start(); + } + mMediaController.setEnabled(true); + mMediaController.updatePausePlay(); + mPrepared = true; + } + + + /** + * Called when the file is finished playing. + * + * Finishes the activity. + * + * @param mp {@link MediaPlayer} instance performing the playback. + */ + @Override + public void onCompletion(MediaPlayer mp) { + Log_OC.e(TAG, "completed"); + if (mp != null) { + mVideoPreview.seekTo(0); + // next lines are necessary to work around undesired video loops + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.GINGERBREAD) { + mVideoPreview.pause(); + + } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.GINGERBREAD_MR1) { + // mVideePreview.pause() is not enough + + mMediaController.setEnabled(false); + mVideoPreview.stopPlayback(); + mAutoplay = false; + mSavedPlaybackPosition = 0; + mVideoPreview.setVideoPath(getFile().getStoragePath()); + } + } // else : called from onError() + mMediaController.updatePausePlay(); + } + + + /** + * Called when an error in playback occurs. + * + * @param mp {@link MediaPlayer} instance performing the playback. + * @param what Type of error + * @param extra Extra code specific to the error + */ + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + if (mVideoPreview.getWindowToken() != null) { + String message = MediaService.getMessageForMediaError(getActivity(), what, extra); + new AlertDialog.Builder(getActivity()) + .setMessage(message) + .setPositiveButton(android.R.string.VideoView_error_button, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + dialog.dismiss(); + VideoHelper.this.onCompletion(null); + } + }) + .setCancelable(false) + .show(); + } + return true; + } + + } + + + @Override + public void onPause() { + super.onPause(); + Log_OC.e(TAG, "onPause"); + } + + @Override + public void onResume() { + super.onResume(); + Log_OC.e(TAG, "onResume"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log_OC.e(TAG, "onDestroy"); + } + + @Override + public void onStop() { + Log_OC.e(TAG, "onStop"); + super.onStop(); + + mPrepared = false; + if (mMediaServiceConnection != null) { + Log_OC.d(TAG, "Unbinding from MediaService ..."); + if (mMediaServiceBinder != null && mMediaController != null) { + mMediaServiceBinder.unregisterMediaController(mMediaController); + } + getActivity().unbindService(mMediaServiceConnection); + mMediaServiceConnection = null; + mMediaServiceBinder = null; + } + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN && v == mVideoPreview) { + startFullScreenVideo(); + return true; + } + return false; + } + + + private void startFullScreenVideo() { + Intent i = new Intent(getActivity(), PreviewVideoActivity.class); + i.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount); + i.putExtra(FileActivity.EXTRA_FILE, getFile()); + i.putExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, mVideoPreview.isPlaying()); + mVideoPreview.pause(); + i.putExtra(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPreview.getCurrentPosition()); + startActivityForResult(i, 0); + } + + @Override + public void onConfigurationChanged (Configuration newConfig) { + Log_OC.e(TAG, "onConfigurationChanged " + this); + } + + @Override + public void onActivityResult (int requestCode, int resultCode, Intent data) { + Log_OC.e(TAG, "onActivityResult " + this); + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == Activity.RESULT_OK) { + mSavedPlaybackPosition = data.getExtras().getInt(PreviewVideoActivity.EXTRA_START_POSITION); + mAutoplay = data.getExtras().getBoolean(PreviewVideoActivity.EXTRA_AUTOPLAY); + } + } + + + private void playAudio() { + OCFile file = getFile(); + if (!mMediaServiceBinder.isPlaying(file)) { + Log_OC.d(TAG, "starting playback of " + file.getStoragePath()); + mMediaServiceBinder.start(mAccount, file, mAutoplay, mSavedPlaybackPosition); + + } else { + if (!mMediaServiceBinder.isPlaying() && mAutoplay) { + mMediaServiceBinder.start(); + mMediaController.updatePausePlay(); + } + } + } + + + private void bindMediaService() { + Log_OC.d(TAG, "Binding to MediaService..."); + if (mMediaServiceConnection == null) { + mMediaServiceConnection = new MediaServiceConnection(); + } + getActivity().bindService( new Intent(getActivity(), + MediaService.class), + mMediaServiceConnection, + Context.BIND_AUTO_CREATE); + // follow the flow in MediaServiceConnection#onServiceConnected(...) + } + + /** Defines callbacks for service binding, passed to bindService() */ + private class MediaServiceConnection implements ServiceConnection { + + @Override + public void onServiceConnected(ComponentName component, IBinder service) { + if (component.equals(new ComponentName(getActivity(), MediaService.class))) { + Log_OC.d(TAG, "Media service connected"); + mMediaServiceBinder = (MediaServiceBinder) service; + if (mMediaServiceBinder != null) { + prepareMediaController(); + playAudio(); // do not wait for the touch of nobody to play audio + + Log_OC.d(TAG, "Successfully bound to MediaService, MediaController ready"); + + } else { + Log_OC.e(TAG, "Unexpected response from MediaService while binding"); + } + } + } + + private void prepareMediaController() { + mMediaServiceBinder.registerMediaController(mMediaController); + if (mMediaController != null) { + mMediaController.setMediaPlayer(mMediaServiceBinder); + mMediaController.setEnabled(true); + mMediaController.updatePausePlay(); + } + } + + @Override + public void onServiceDisconnected(ComponentName component) { + if (component.equals(new ComponentName(getActivity(), MediaService.class))) { + Log_OC.e(TAG, "Media service suddenly disconnected"); + if (mMediaController != null) { + mMediaController.setMediaPlayer(null); + } else { + Toast.makeText(getActivity(), "No media controller to release when disconnected from media service", Toast.LENGTH_SHORT).show(); + } + mMediaServiceBinder = null; + mMediaServiceConnection = null; + } + } + } + + + + /** + * Opens the previewed file with an external application. + * + * TODO - improve this; instead of prioritize the actions available for the MIME type in the server, + * we should get a list of available apps for MIME tpye in the server and join it with the list of + * available apps for the MIME type known from the file extension, to let the user choose + */ + private void openFile() { + OCFile file = getFile(); + stopPreview(true); + String storagePath = file.getStoragePath(); + String encodedStoragePath = WebdavUtils.encodePath(storagePath); + try { + Intent i = new Intent(Intent.ACTION_VIEW); + i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), file.getMimetype()); + i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + startActivity(i); + + } catch (Throwable t) { + Log_OC.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + file.getMimetype()); + boolean toastIt = true; + String mimeType = ""; + try { + Intent i = new Intent(Intent.ACTION_VIEW); + mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1)); + if (mimeType == null || !mimeType.equals(file.getMimetype())) { + if (mimeType != null) { + i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType); + } else { + // desperate try + i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*-/*"); + } + i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + startActivity(i); + toastIt = false; + } + + } catch (IndexOutOfBoundsException e) { + Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath); + + } catch (ActivityNotFoundException e) { + Log_OC.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension"); + + } catch (Throwable th) { + Log_OC.e(TAG, "Unexpected problem when opening: " + storagePath, th); + + } finally { + if (toastIt) { + Toast.makeText(getActivity(), "There is no application to handle file " + file.getFileName(), Toast.LENGTH_SHORT).show(); + } + } + + } + finish(); + } + + /** + * Starts a the removal of the previewed file. + * + * Shows a confirmation dialog. The action continues in {@link #onConfirmation(String)} , {@link #onNeutral(String)} or {@link #onCancel(String)}, + * depending upon the user selection in the dialog. + */ + private void removeFile() { + ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance( + R.string.confirmation_remove_alert, + new String[]{getFile().getFileName()}, + R.string.confirmation_remove_remote_and_local, + R.string.confirmation_remove_local, + R.string.common_cancel); + confDialog.setOnConfirmationListener(this); + confDialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION); + } + + + /** + * Performs the removal of the previewed file, both locally and in the server. + */ + @Override + public void onConfirmation(String callerTag) { + OCFile file = getFile(); + if (mStorageManager.getFileById(file.getFileId()) != null) { // check that the file is still there; + stopPreview(true); + mLastRemoteOperation = new RemoveFileOperation( file, // TODO we need to review the interface with RemoteOperations, and use OCFile IDs instead of OCFile objects as parameters + true, + mStorageManager); + mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); + + ((FileDisplayActivity) getActivity()).showLoadingDialog(); + } + } + + + /** + * Removes the file from local storage + */ + @Override + public void onNeutral(String callerTag) { + // TODO this code should be made in a secondary thread, + OCFile file = getFile(); + if (file.isDown()) { // checks it is still there + stopPreview(true); + File f = new File(file.getStoragePath()); + f.delete(); + file.setStoragePath(null); + mStorageManager.saveFile(file); + finish(); + } + } + + /** + * User cancelled the removal action. + */ + @Override + public void onCancel(String callerTag) { + // nothing to do here + } + + + /** + * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewMediaFragment} to be previewed. + * + * @param file File to test if can be previewed. + * @return 'True' if the file can be handled by the fragment. + */ + public static boolean canBePreviewed(OCFile file) { + return (file != null && (file.isAudio() || file.isVideo())); + } + + /** + * {@inheritDoc} + */ + @Override + public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { + if (operation.equals(mLastRemoteOperation)) { + if (operation instanceof RemoveFileOperation) { + onRemoveFileOperationFinish((RemoveFileOperation)operation, result); + } + } + } + + private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) { + ((FileDisplayActivity) getActivity()).dismissLoadingDialog(); + if (result.isSuccess()) { + Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG); + msg.show(); + finish(); + + } else { + Toast msg = Toast.makeText(getActivity(), R.string.remove_fail_msg, Toast.LENGTH_LONG); + msg.show(); + if (result.isSslRecoverableException()) { + // TODO show the SSL warning dialog + } + } + } + + private void stopPreview(boolean stopAudio) { + OCFile file = getFile(); + if (file.isAudio() && stopAudio) { + mMediaServiceBinder.pause(); + + } else if (file.isVideo()) { + mVideoPreview.stopPlayback(); + } + } + + + + /** + * Finishes the preview + */ + private void finish() { + getActivity().onBackPressed(); + } + + + public int getPosition() { + if (mPrepared) { + mSavedPlaybackPosition = mVideoPreview.getCurrentPosition(); + } + Log_OC.e(TAG, "getting position: " + mSavedPlaybackPosition); + return mSavedPlaybackPosition; + } + + public boolean isPlaying() { + if (mPrepared) { + mAutoplay = mVideoPreview.isPlaying(); + } + return mAutoplay; + } + +} diff --git a/src/com/owncloud/android/ui/preview/PreviewVideoActivity.java b/src/com/owncloud/android/ui/preview/PreviewVideoActivity.java new file mode 100644 index 00000000..56972c65 --- /dev/null +++ b/src/com/owncloud/android/ui/preview/PreviewVideoActivity.java @@ -0,0 +1,240 @@ +/* ownCloud Android client application + * 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.preview; + +import com.owncloud.android.Log_OC; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.authentication.AccountUtils.AccountNotFoundException; +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.media.MediaService; +import com.owncloud.android.ui.activity.FileActivity; + +import android.accounts.Account; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnCompletionListener; +import android.media.MediaPlayer.OnErrorListener; +import android.media.MediaPlayer.OnPreparedListener; +import android.net.Uri; +import android.os.Bundle; +import android.widget.MediaController; +import android.widget.VideoView; + + +/** + * Activity implementing a basic video player. + * + * Used as an utility to preview video files contained in an ownCloud account. + * + * Currently, it always plays in landscape mode, full screen. When the playback ends, + * the activity is finished. + * + * @author David A. Velasco + */ +public class PreviewVideoActivity extends FileActivity implements OnCompletionListener, OnPreparedListener, OnErrorListener { + + /** Key to receive a flag signaling if the video should be started immediately */ + public static final String EXTRA_AUTOPLAY = "AUTOPLAY"; + + /** Key to receive the position of the playback where the video should be put at start */ + public static final String EXTRA_START_POSITION = "START_POSITION"; + + private static final String TAG = PreviewVideoActivity.class.getSimpleName(); + + private DataStorageManager mStorageManager; + + private int mSavedPlaybackPosition; // in the unit time handled by MediaPlayer.getCurrentPosition() + private boolean mAutoplay; // when 'true', the playback starts immediately with the activity + private VideoView mVideoPlayer; // view to play the file; both performs and show the playback + private MediaController mMediaController; // panel control used by the user to control the playback + + /** + * Called when the activity is first created. + * + * Searches for an {@link OCFile} and ownCloud {@link Account} holding it in the starting {@link Intent}. + * + * The {@link Account} is unnecessary if the file is downloaded; else, the {@link Account} is used to + * try to stream the remote file - TODO get the streaming works + * + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log_OC.e(TAG, "ACTIVITY\t\tonCreate"); + + setContentView(R.layout.video_layout); + + if (savedInstanceState == null) { + Bundle extras = getIntent().getExtras(); + mSavedPlaybackPosition = extras.getInt(EXTRA_START_POSITION); + mAutoplay = extras.getBoolean(EXTRA_AUTOPLAY); + + } else { + mSavedPlaybackPosition = savedInstanceState.getInt(EXTRA_START_POSITION); + mAutoplay = savedInstanceState.getBoolean(EXTRA_AUTOPLAY); + } + + mVideoPlayer = (VideoView) findViewById(R.id.videoPlayer); + + // set listeners to get more contol on the playback + mVideoPlayer.setOnPreparedListener(this); + mVideoPlayer.setOnCompletionListener(this); + mVideoPlayer.setOnErrorListener(this); + + // keep the screen on while the playback is performed (prevents screen off by battery save) + mVideoPlayer.setKeepScreenOn(true); + } + + + /** + * {@inheritDoc} + */ + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + Log_OC.e(TAG, "ACTIVITY\t\tonSaveInstanceState"); + outState.putInt(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPlayer.getCurrentPosition()); + outState.putBoolean(PreviewVideoActivity.EXTRA_AUTOPLAY , mVideoPlayer.isPlaying()); + } + + + @Override + public void onBackPressed() { + Log_OC.e(TAG, "ACTIVTIY\t\tonBackPressed"); + Intent i = new Intent(); + i.putExtra(EXTRA_AUTOPLAY, mVideoPlayer.isPlaying()); + i.putExtra(EXTRA_START_POSITION, mVideoPlayer.getCurrentPosition()); + setResult(RESULT_OK, i); + super.onBackPressed(); + } + + + /** + * Called when the file is ready to be played. + * + * Just starts the playback. + * + * @param mp {@link MediaPlayer} instance performing the playback. + */ + @Override + public void onPrepared(MediaPlayer mp) { + Log_OC.e(TAG, "ACTIVITY\t\tonPrepare"); + mVideoPlayer.seekTo(mSavedPlaybackPosition); + if (mAutoplay) { + mVideoPlayer.start(); + } + mMediaController.show(5000); + } + + + /** + * Called when the file is finished playing. + * + * Rewinds the video + * + * @param mp {@link MediaPlayer} instance performing the playback. + */ + @Override + public void onCompletion(MediaPlayer mp) { + mVideoPlayer.seekTo(0); + } + + + /** + * Called when an error in playback occurs. + * + * @param mp {@link MediaPlayer} instance performing the playback. + * @param what Type of error + * @param extra Extra code specific to the error + */ + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + Log_OC.e(TAG, "Error in video playback, what = " + what + ", extra = " + extra); + + if (mMediaController != null) { + mMediaController.hide(); + } + + if (mVideoPlayer.getWindowToken() != null) { + String message = MediaService.getMessageForMediaError(this, what, extra); + new AlertDialog.Builder(this) + .setMessage(message) + .setPositiveButton(android.R.string.VideoView_error_button, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + PreviewVideoActivity.this.onCompletion(null); + } + }) + .setCancelable(false) + .show(); + } + return true; + } + + + @Override + protected void onAccountSet(boolean stateWasRecovered) { + if (getAccount() != null) { + OCFile file = getFile(); + /// Validate handled file (first image to preview) + if (file == null) { + throw new IllegalStateException("Instanced with a NULL OCFile"); + } + if (!file.isVideo()) { + throw new IllegalArgumentException("Non-video file passed as argument"); + } + mStorageManager = new FileDataStorageManager(getAccount(), getContentResolver()); + file = mStorageManager.getFileById(file.getFileId()); + if (file != null) { + if (file.isDown()) { + mVideoPlayer.setVideoPath(file.getStoragePath()); + + } else { + // not working yet + String url; + try { + url = AccountUtils.constructFullURLForAccount(this, getAccount()) + file.getRemotePath(); + mVideoPlayer.setVideoURI(Uri.parse(url)); + } catch (AccountNotFoundException e) { + onError(null, MediaService.OC_MEDIA_ERROR, R.string.media_err_no_account); + } + } + + // create and prepare control panel for the user + mMediaController = new MediaController(this); + mMediaController.setMediaPlayer(mVideoPlayer); + mMediaController.setAnchorView(mVideoPlayer); + mVideoPlayer.setMediaController(mMediaController); + + } else { + finish(); + } + } else { + Log_OC.wtf(TAG, "onAccountChanged was called with NULL account associated!"); + finish(); + } + } + + +} \ No newline at end of file diff --git a/src/com/owncloud/android/utils/FileStorageUtils.java b/src/com/owncloud/android/utils/FileStorageUtils.java new file mode 100644 index 00000000..0c7e9911 --- /dev/null +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@ -0,0 +1,80 @@ +/* ownCloud Android client application + * 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.utils; + +import java.io.File; + +import com.owncloud.android.MainApp; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.OCFile; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.net.Uri; +import android.os.Environment; +import android.os.StatFs; + + +/** + * Static methods to help in access to local file system. + * + * @author David A. Velasco + */ +public class FileStorageUtils { + //private static final String LOG_TAG = "FileStorageUtils"; + + 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 + } + + public static final String getDefaultSavePathFor(String accountName, OCFile file) { + return getSavePath(accountName) + file.getRemotePath(); + } + + 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 + } + + @SuppressLint("NewApi") + public static final long getUsableSpace(String accountName) { + File savePath = Environment.getExternalStorageDirectory(); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD) { + return savePath.getUsableSpace(); + + } else { + StatFs stats = new StatFs(savePath.getAbsolutePath()); + return stats.getAvailableBlocks() * stats.getBlockSize(); + } + + } + + public static final String getLogPath() { + return Environment.getExternalStorageDirectory() + File.separator + MainApp.getDataFolder() + File.separator + "log"; + } + + public static String getInstantUploadFilePath(Context context, String fileName) { + String uploadPath = context.getString(R.string.instant_upload_path); + String value = uploadPath + OCFile.PATH_SEPARATOR + (fileName == null ? "" : fileName); + return value; + } + +} \ No newline at end of file diff --git a/src/com/owncloud/android/utils/OwnCloudVersion.java b/src/com/owncloud/android/utils/OwnCloudVersion.java new file mode 100644 index 00000000..630e7acd --- /dev/null +++ b/src/com/owncloud/android/utils/OwnCloudVersion.java @@ -0,0 +1,85 @@ +/* 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.utils; + +public class OwnCloudVersion implements Comparable { + public static final OwnCloudVersion owncloud_v1 = new OwnCloudVersion( + 0x010000); + public static final OwnCloudVersion owncloud_v2 = new OwnCloudVersion( + 0x020000); + public static final OwnCloudVersion owncloud_v3 = new OwnCloudVersion( + 0x030000); + public static final OwnCloudVersion owncloud_v4 = new OwnCloudVersion( + 0x040000); + public static final OwnCloudVersion owncloud_v4_5 = new OwnCloudVersion( + 0x040500); + + // format is in version + // 0xAABBCC + // for version AA.BB.CC + // ie version 2.0.3 will be stored as 0x030003 + private int mVersion; + private boolean mIsValid; + + public OwnCloudVersion(int version) { + mVersion = version; + mIsValid = true; + } + + public OwnCloudVersion(String version) { + mVersion = 0; + mIsValid = false; + parseVersionString(version); + } + + public String toString() { + return ((mVersion >> 16) % 256) + "." + ((mVersion >> 8) % 256) + "." + + ((mVersion) % 256); + } + + public boolean isVersionValid() { + return mIsValid; + } + + @Override + public int compareTo(OwnCloudVersion another) { + return another.mVersion == mVersion ? 0 + : another.mVersion < mVersion ? 1 : -1; + } + + private void parseVersionString(String version) { + try { + String[] nums = version.split("\\."); + if (nums.length > 0) { + mVersion += Integer.parseInt(nums[0]); + } + mVersion = mVersion << 8; + if (nums.length > 1) { + mVersion += Integer.parseInt(nums[1]); + } + mVersion = mVersion << 8; + if (nums.length > 2) { + mVersion += Integer.parseInt(nums[2]); + } + mIsValid = true; + } catch (Exception e) { + mIsValid = false; + } + } +} diff --git a/src/com/owncloud/android/utils/RecursiveFileObserver.java b/src/com/owncloud/android/utils/RecursiveFileObserver.java new file mode 100644 index 00000000..be44f8f6 --- /dev/null +++ b/src/com/owncloud/android/utils/RecursiveFileObserver.java @@ -0,0 +1,101 @@ +/* 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.utils; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +import android.os.FileObserver; + +public class RecursiveFileObserver extends FileObserver { + + public static int CHANGES_ONLY = CLOSE_WRITE | MOVE_SELF | MOVED_FROM; + + List mObservers; + String mPath; + int mMask; + + public RecursiveFileObserver(String path) { + this(path, ALL_EVENTS); + } + + public RecursiveFileObserver(String path, int mask) { + super(path, mask); + mPath = path; + mMask = mask; + } + + @Override + public void startWatching() { + if (mObservers != null) return; + mObservers = new ArrayList(); + Stack stack = new Stack(); + stack.push(mPath); + + while (!stack.empty()) { + String parent = stack.pop(); + mObservers.add(new SingleFileObserver(parent, mMask)); + File path = new File(parent); + File[] files = path.listFiles(); + if (files == null) continue; + for (int i = 0; i < files.length; ++i) { + if (files[i].isDirectory() && !files[i].getName().equals(".") + && !files[i].getName().equals("..")) { + stack.push(files[i].getPath()); + } + } + } + for (int i = 0; i < mObservers.size(); i++) + mObservers.get(i).startWatching(); + } + + @Override + public void stopWatching() { + if (mObservers == null) return; + + for (int i = 0; i < mObservers.size(); ++i) + mObservers.get(i).stopWatching(); + + mObservers.clear(); + mObservers = null; + } + + @Override + public void onEvent(int event, String path) { + + } + + private class SingleFileObserver extends FileObserver { + private String mPath; + + public SingleFileObserver(String path, int mask) { + super(path, mask); + mPath = path; + } + + @Override + public void onEvent(int event, String path) { + String newPath = mPath + "/" + path; + RecursiveFileObserver.this.onEvent(event, newPath); + } + + } +} diff --git a/src/com/owncloud/android/widgets/ActionEditText.java b/src/com/owncloud/android/widgets/ActionEditText.java new file mode 100644 index 00000000..1077d154 --- /dev/null +++ b/src/com/owncloud/android/widgets/ActionEditText.java @@ -0,0 +1,146 @@ +/* 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.widgets; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import com.owncloud.android.R; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.EditText; + +public class ActionEditText extends EditText { + private String s; + private String optionOneString; + private int optionOneColor; + private String optionTwoString; + private int optionTwoColor; + private Rect mTextBounds, mButtonRect; + + private String badgeClickCallback; + private Rect btn_rect; + + public ActionEditText(Context context, AttributeSet attrs) { + super(context, attrs); + getAttrs(attrs); + s = optionOneString; + mTextBounds = new Rect(); + mButtonRect = new Rect(); + } + + public ActionEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + getAttrs(attrs); + s = optionOneString; + mTextBounds = new Rect(); + mButtonRect = new Rect(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + Paint p = getPaint(); + + p.getTextBounds(s, 0, s.length(), mTextBounds); + + getDrawingRect(mButtonRect); + mButtonRect.top += 10; + mButtonRect.bottom -= 10; + mButtonRect.left = (int) (getWidth() - mTextBounds.width() - 18); + mButtonRect.right = getWidth() - 10; + btn_rect = mButtonRect; + + if (s.equals(optionOneString)) + p.setColor(optionOneColor); + else + p.setColor(optionTwoColor); + canvas.drawRect(mButtonRect, p); + p.setColor(Color.GRAY); + + canvas.drawText(s, mButtonRect.left + 3, mButtonRect.bottom + - (mTextBounds.height() / 2), p); + + invalidate(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + int touchX = (int) event.getX(); + int touchY = (int) event.getY(); + boolean r = super.onTouchEvent(event); + if (event.getAction() == MotionEvent.ACTION_UP) { + if (btn_rect.contains(touchX, touchY)) { + if (s.equals(optionTwoString)) + s = optionOneString; + else + s = optionTwoString; + if (badgeClickCallback != null) { + @SuppressWarnings("rawtypes") + Class[] paramtypes = new Class[2]; + paramtypes[0] = android.view.View.class; + paramtypes[1] = String.class; + Method method; + try { + + method = getContext().getClass().getMethod( + badgeClickCallback, paramtypes); + method.invoke(getContext(), this, s); + + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + + invalidate(); + } + } + } + return r; + } + + private void getAttrs(AttributeSet attr) { + TypedArray a = getContext().obtainStyledAttributes(attr, + R.styleable.ActionEditText); + optionOneString = a + .getString(R.styleable.ActionEditText_optionOneString); + optionTwoString = a + .getString(R.styleable.ActionEditText_optionTwoString); + optionOneColor = a.getColor(R.styleable.ActionEditText_optionOneColor, + 0x00ff00); + optionTwoColor = a.getColor(R.styleable.ActionEditText_optionTwoColor, + 0xff0000); + badgeClickCallback = a + .getString(R.styleable.ActionEditText_onBadgeClick); + } + +} diff --git a/src/de/mobilcom/debitel/cloud/android/DisplayUtils.java b/src/de/mobilcom/debitel/cloud/android/DisplayUtils.java deleted file mode 100644 index 4b177554..00000000 --- a/src/de/mobilcom/debitel/cloud/android/DisplayUtils.java +++ /dev/null @@ -1,190 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android; - -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; - -/** - * A helper class for some string operations. - * - * @author Bartek Przybylski - * @author David A. Velasco - */ -public class DisplayUtils { - - //private static String TAG = DisplayUtils.class.getSimpleName(); - - private static final String[] sizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; - - private static HashMap mimeType2HUmanReadable; - static { - mimeType2HUmanReadable = new HashMap(); - // images - mimeType2HUmanReadable.put("image/jpeg", "JPEG image"); - mimeType2HUmanReadable.put("image/jpg", "JPEG image"); - mimeType2HUmanReadable.put("image/png", "PNG image"); - mimeType2HUmanReadable.put("image/bmp", "Bitmap image"); - mimeType2HUmanReadable.put("image/gif", "GIF image"); - mimeType2HUmanReadable.put("image/svg+xml", "JPEG image"); - mimeType2HUmanReadable.put("image/tiff", "TIFF image"); - // music - mimeType2HUmanReadable.put("audio/mpeg", "MP3 music file"); - mimeType2HUmanReadable.put("application/ogg", "OGG music file"); - - } - - private static final String TYPE_APPLICATION = "application"; - private static final String TYPE_AUDIO = "audio"; - private static final String TYPE_IMAGE = "image"; - private static final String TYPE_TXT = "text"; - private static final String TYPE_VIDEO = "video"; - - private static final String SUBTYPE_PDF = "pdf"; - private static final String[] SUBTYPES_DOCUMENT = { "msword", "mspowerpoint", "msexcel", - "vnd.oasis.opendocument.presentation", - "vnd.oasis.opendocument.spreadsheet", - "vnd.oasis.opendocument.text" - }; - private static Set SUBTYPES_DOCUMENT_SET = new HashSet(Arrays.asList(SUBTYPES_DOCUMENT)); - private static final String[] SUBTYPES_COMPRESSED = {"x-tar", "x-gzip", "zip"}; - private static final Set SUBTYPES_COMPRESSED_SET = new HashSet(Arrays.asList(SUBTYPES_COMPRESSED)); - - /** - * Converts the file size in bytes to human readable output. - * - * @param bytes Input file size - * @return Like something readable like "12 MB" - */ - public static String bytesToHumanReadable(long bytes) { - double result = bytes; - int attachedsuff = 0; - while (result > 1024 && attachedsuff < sizeSuffixes.length) { - result /= 1024.; - attachedsuff++; - } - result = ((int) (result * 100)) / 100.; - return result + " " + sizeSuffixes[attachedsuff]; - } - - /** - * Removes special HTML entities from a string - * - * @param s Input string - * @return A cleaned version of the string - */ - public static String HtmlDecode(String s) { - /* - * TODO: Perhaps we should use something more proven like: - * http://commons.apache.org/lang/api-2.6/org/apache/commons/lang/StringEscapeUtils.html#unescapeHtml%28java.lang.String%29 - */ - - String ret = ""; - for (int i = 0; i < s.length(); ++i) { - if (s.charAt(i) == '%') { - ret += (char) Integer.parseInt(s.substring(i + 1, i + 3), 16); - i += 2; - } else { - ret += s.charAt(i); - } - } - return ret; - } - - /** - * Converts MIME types like "image/jpg" to more end user friendly output - * like "JPG image". - * - * @param mimetype MIME type to convert - * @return A human friendly version of the MIME type - */ - public static String convertMIMEtoPrettyPrint(String mimetype) { - if (mimeType2HUmanReadable.containsKey(mimetype)) { - return mimeType2HUmanReadable.get(mimetype); - } - if (mimetype.split("/").length >= 2) - return mimetype.split("/")[1].toUpperCase() + " file"; - return "Unknown type"; - } - - - /** - * Returns the resource identifier of an image resource to use as icon associated to a - * known MIME type. - * - * @param mimetype MIME type string. - * @return Resource identifier of an image resource. - */ - public static int getResourceId(String mimetype) { - - if (mimetype == null || "DIR".equals(mimetype)) { - return R.drawable.ic_menu_archive; - - } else { - String [] parts = mimetype.split("/"); - String type = parts[0]; - String subtype = (parts.length > 1) ? parts[1] : ""; - - if(TYPE_TXT.equals(type)) { - return R.drawable.file_doc; - - } else if(TYPE_IMAGE.equals(type)) { - return R.drawable.file_image; - - } else if(TYPE_VIDEO.equals(type)) { - return R.drawable.file_movie; - - } else if(TYPE_AUDIO.equals(type)) { - return R.drawable.file_sound; - - } else if(TYPE_APPLICATION.equals(type)) { - - if (SUBTYPE_PDF.equals(subtype)) { - return R.drawable.file_pdf; - - } else if (SUBTYPES_DOCUMENT_SET.contains(subtype)) { - return R.drawable.file_doc; - - } else if (SUBTYPES_COMPRESSED_SET.contains(subtype)) { - return R.drawable.file_zip; - } - - } - // problems: RAR, RTF, 3GP are send as application/octet-stream from the server ; extension in the filename should be explicitly reviewed - } - - // default icon - return R.drawable.file; - } - - - - /** - * Converts Unix time to human readable format - * @param miliseconds that have passed since 01/01/1970 - * @return The human readable time for the users locale - */ - public static String unixTimeToHumanReadable(long milliseconds) { - Date date = new Date(milliseconds); - return date.toLocaleString(); - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/Log_OC.java b/src/de/mobilcom/debitel/cloud/android/Log_OC.java deleted file mode 100644 index 48d42779..00000000 --- a/src/de/mobilcom/debitel/cloud/android/Log_OC.java +++ /dev/null @@ -1,127 +0,0 @@ -package de.mobilcom.debitel.cloud.android; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -import android.util.Log; - - - -public class Log_OC { - - - private static boolean isEnabled = false; - private static File logFile; - private static File folder; - private static BufferedWriter buf; - - public static void i(String TAG, String message){ - // Printing the message to LogCat console - Log.i(TAG, message); - // Write the log message to the file - appendLog(TAG+" : "+message); - } - - public static void d(String TAG, String message){ - Log.d(TAG, message); - appendLog(TAG + " : " + message); - } - public static void d(String TAG, String message, Exception e) { - Log.d(TAG, message, e); - appendLog(TAG + " : " + message + " Exception : "+ e.getStackTrace()); - } - public static void e(String TAG, String message){ - Log.e(TAG, message); - appendLog(TAG + " : " + message); - } - - public static void e(String TAG, String message, Throwable e) { - Log.e(TAG, message, e); - appendLog(TAG+" : " + message +" Exception : " + e.getStackTrace()); - } - - public static void v(String TAG, String message){ - Log.v(TAG, message); - appendLog(TAG+" : "+ message); - } - - public static void w(String TAG, String message) { - Log.w(TAG,message); - appendLog(TAG+" : "+ message); - } - - public static void wtf(String TAG, String message) { - Log.wtf(TAG,message); - appendLog(TAG+" : "+ message); - } - - public static void startLogging(String logPath) { - folder = new File(logPath); - logFile = new File(folder + File.separator + "log.txt"); - - if (!folder.exists()) { - folder.mkdirs(); - } - if (logFile.exists()) { - logFile.delete(); - } - try { - logFile.createNewFile(); - buf = new BufferedWriter(new FileWriter(logFile, true)); - isEnabled = true; - appendPhoneInfo(); - }catch (IOException e){ - e.printStackTrace(); - } - } - - public static void stopLogging() { - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()); - String currentDateandTime = sdf.format(new Date()); - if (logFile != null) { - logFile.renameTo(new File(folder + File.separator + MainApp.getLogName() + currentDateandTime+".log")); - - isEnabled = false; - try { - buf.close(); - } catch (IOException e) { - e.printStackTrace(); - } - - } - - } - - private static void appendPhoneInfo() { - appendLog("Model : " + android.os.Build.MODEL); - appendLog("Brand : " + android.os.Build.BRAND); - appendLog("Product : " + android.os.Build.PRODUCT); - appendLog("Device : " + android.os.Build.DEVICE); - appendLog("Version-Codename : " + android.os.Build.VERSION.CODENAME); - appendLog("Version-Release : " + android.os.Build.VERSION.RELEASE); - } - - private static void appendLog(String text) { - if (isEnabled) { - try { - buf.append(text); - buf.newLine(); - } catch (IOException e) { - e.printStackTrace(); - } - } -} - - - - - - - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/MainApp.java b/src/de/mobilcom/debitel/cloud/android/MainApp.java deleted file mode 100644 index 21bf3055..00000000 --- a/src/de/mobilcom/debitel/cloud/android/MainApp.java +++ /dev/null @@ -1,105 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android; - -import android.app.Application; -import android.content.Context; -/** - * Main Application of the project - * - * Contains methods to build the "static" strings. These strings were before constants in different classes - * - * @author masensio - */ -public class MainApp extends Application { - - private static Context mContext; - - public void onCreate(){ - super.onCreate(); - MainApp.mContext = getApplicationContext(); - } - - public static Context getAppContext() { - return MainApp.mContext; - } - - // Methods to obtain Strings referring app_name - // From AccountAuthenticator - // public static final String ACCOUNT_TYPE = "owncloud"; - public static String getAccountType() { - return getAppContext().getResources().getString(R.string.account_type); - } - - // From AccountAuthenticator - // public static final String AUTHORITY = "org.owncloud"; - public static String getAuthority() { - return getAppContext().getResources().getString(R.string.authority); - } - - // From AccountAuthenticator - // public static final String AUTH_TOKEN_TYPE = "org.owncloud"; - public static String getAuthTokenType() { - return getAppContext().getResources().getString(R.string.authority); - } - - // From AccountAuthenticator - // public static final String AUTH_TOKEN_TYPE_PASSWORD = "owncloud.password"; - public static String getAuthTokenTypePass() { - return getAppContext().getResources().getString(R.string.account_type) + ".password"; - } - - // From AccountAuthenticator - // public static final String AUTH_TOKEN_TYPE_ACCESS_TOKEN = "owncloud.oauth2.access_token"; - public static String getAuthTokenTypeAccessToken() { - return getAppContext().getResources().getString(R.string.account_type) + ".oauth2.access_token"; - } - - // From AccountAuthenticator - // public static final String AUTH_TOKEN_TYPE_REFRESH_TOKEN = "owncloud.oauth2.refresh_token"; - public static String getAuthTokenTypeRefreshToken() { - return getAppContext().getResources().getString(R.string.account_type) + ".oauth2.refresh_token"; - } - - // From AccountAuthenticator - // public static final String AUTH_TOKEN_TYPE_SAML_WEB_SSO_SESSION_COOKIE = "owncloud.saml.web_sso.session_cookie"; - public static String getAuthTokenTypeSamlSessionCookie() { - return getAppContext().getResources().getString(R.string.account_type) + ".saml.web_sso.session_cookie"; - } - - // From ProviderMeta - // public static final String DB_FILE = "owncloud.db"; - public static String getDBFile() { - return getAppContext().getResources().getString(R.string.db_file); - } - - // From ProviderMeta - // private final String mDatabaseName = "ownCloud"; - public static String getDBName() { - return getAppContext().getResources().getString(R.string.db_name); - } - - // data_folder - public static String getDataFolder() { - return getAppContext().getResources().getString(R.string.data_folder); - } - - // log_name - public static String getLogName() { - return getAppContext().getResources().getString(R.string.log_name); - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/OwnCloudSession.java b/src/de/mobilcom/debitel/cloud/android/OwnCloudSession.java deleted file mode 100644 index 4e815784..00000000 --- a/src/de/mobilcom/debitel/cloud/android/OwnCloudSession.java +++ /dev/null @@ -1,56 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android; - -/** - * Represents a session to an ownCloud instance - * - * @author Bartek Przybylski - * - */ -public class OwnCloudSession { - private String mSessionName; - private String mSessionUrl; - private int mEntryId; - - public OwnCloudSession(String name, String url, int entryId) { - mSessionName = name; - mSessionUrl = url; - mEntryId = entryId; - } - - public void setName(String name) { - mSessionName = name; - } - - public String getName() { - return mSessionName; - } - - public void setUrl(String url) { - mSessionUrl = url; - } - - public String getUrl() { - return mSessionUrl; - } - - public int getEntryId() { - return mEntryId; - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/Uploader.java b/src/de/mobilcom/debitel/cloud/android/Uploader.java deleted file mode 100644 index d7881365..00000000 --- a/src/de/mobilcom/debitel/cloud/android/Uploader.java +++ /dev/null @@ -1,428 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android; - -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.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.app.Dialog; -import android.app.ListActivity; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcelable; -import android.provider.MediaStore.Audio; -import android.provider.MediaStore.Images; -import android.provider.MediaStore.Video; -import android.view.View; -import android.view.Window; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.EditText; -import android.widget.SimpleAdapter; -import android.widget.Toast; - - -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.authentication.AccountAuthenticator; -import de.mobilcom.debitel.cloud.android.datamodel.DataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.FileDataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.files.services.FileUploader; -import de.mobilcom.debitel.cloud.android.ui.CustomButton; - -/** - * This can be used to upload things to an ownCloud instance. - * - * @author Bartek Przybylski - * - */ -public class Uploader extends ListActivity implements OnItemClickListener, android.view.View.OnClickListener { - private static final String TAG = "ownCloudUploader"; - - private Account mAccount; - private AccountManager mAccountManager; - private Stack mParents; - private ArrayList mStreamsToUpload; - private boolean mCreateDir; - private String mUploadPath; - private DataStorageManager mStorageManager; - private OCFile mFile; - - private final static int DIALOG_NO_ACCOUNT = 0; - private final static int DIALOG_WAITING = 1; - private final static int DIALOG_NO_STREAM = 2; - private final static int DIALOG_MULTIPLE_ACCOUNT = 3; - - private final static int REQUEST_CODE_SETUP_ACCOUNT = 0; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().requestFeature(Window.FEATURE_NO_TITLE); - mParents = new Stack(); - mParents.add(""); - if (prepareStreamsToUpload()) { - mAccountManager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE); - Account[] accounts = mAccountManager.getAccountsByType(MainApp.getAccountType()); - if (accounts.length == 0) { - Log_OC.i(TAG, "No ownCloud account is available"); - showDialog(DIALOG_NO_ACCOUNT); - } else if (accounts.length > 1) { - Log_OC.i(TAG, "More then one ownCloud is available"); - showDialog(DIALOG_MULTIPLE_ACCOUNT); - } else { - mAccount = accounts[0]; - mStorageManager = new FileDataStorageManager(mAccount, getContentResolver()); - populateDirectoryList(); - } - } else { - showDialog(DIALOG_NO_STREAM); - } - } - - @Override - protected Dialog onCreateDialog(final int id) { - final AlertDialog.Builder builder = new Builder(this); - switch (id) { - case DIALOG_WAITING: - ProgressDialog pDialog = new ProgressDialog(this); - pDialog.setIndeterminate(false); - pDialog.setCancelable(false); - pDialog.setMessage(getResources().getString(R.string.uploader_info_uploading)); - return pDialog; - case DIALOG_NO_ACCOUNT: - 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))); - builder.setCancelable(false); - builder.setPositiveButton(R.string.uploader_wrn_no_account_setup_btn_text, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ECLAIR_MR1) { - // using string value since in API7 this - // constatn is not defined - // in API7 < this constatant is defined in - // Settings.ADD_ACCOUNT_SETTINGS - // and Settings.EXTRA_AUTHORITIES - Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT); - intent.putExtra("authorities", new String[] { MainApp.getAuthTokenType() }); - startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT); - } else { - // since in API7 there is no direct call for - // account setup, so we need to - // show our own AccountSetupAcricity, get - // desired results and setup - // everything for ourself - Intent intent = new Intent(getBaseContext(), AccountAuthenticator.class); - startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT); - } - } - }); - builder.setNegativeButton(R.string.uploader_wrn_no_account_quit_btn_text, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - return builder.create(); - case DIALOG_MULTIPLE_ACCOUNT: - CharSequence ac[] = new CharSequence[mAccountManager.getAccountsByType(MainApp.getAccountType()).length]; - for (int i = 0; i < ac.length; ++i) { - ac[i] = mAccountManager.getAccountsByType(MainApp.getAccountType())[i].name; - } - builder.setTitle(R.string.common_choose_account); - builder.setItems(ac, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mAccount = mAccountManager.getAccountsByType(MainApp.getAccountType())[which]; - mStorageManager = new FileDataStorageManager(mAccount, getContentResolver()); - populateDirectoryList(); - } - }); - builder.setCancelable(true); - builder.setOnCancelListener(new OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - dialog.cancel(); - finish(); - } - }); - return builder.create(); - case DIALOG_NO_STREAM: - builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setTitle(R.string.uploader_wrn_no_content_title); - builder.setMessage(R.string.uploader_wrn_no_content_text); - builder.setCancelable(false); - builder.setNegativeButton(R.string.common_cancel, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - return builder.create(); - default: - throw new IllegalArgumentException("Unknown dialog id: " + id); - } - } - - class a implements OnClickListener { - String mPath; - EditText mDirname; - - public a(String path, EditText dirname) { - mPath = path; - mDirname = dirname; - } - - @Override - public void onClick(DialogInterface dialog, int which) { - Uploader.this.mUploadPath = mPath + mDirname.getText().toString(); - Uploader.this.mCreateDir = true; - uploadFiles(); - } - } - - @Override - public void onBackPressed() { - - if (mParents.size() <= 1) { - super.onBackPressed(); - return; - } else { - mParents.pop(); - populateDirectoryList(); - } - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - // click on folder in the list - Log_OC.d(TAG, "on item click"); - Vector tmpfiles = mStorageManager.getDirectoryContent(mFile); - if (tmpfiles.size() <= 0) return; - // filter on dirtype - Vector files = new Vector(); - for (OCFile f : tmpfiles) - if (f.isDirectory()) - files.add(f); - if (files.size() < position) { - throw new IndexOutOfBoundsException("Incorrect item selected"); - } - mParents.push(files.get(position).getFileName()); - populateDirectoryList(); - } - - @Override - public void onClick(View v) { - // click on button - switch (v.getId()) { - case R.id.uploader_choose_folder: - mUploadPath = ""; // first element in mParents is root dir, represented by ""; init mUploadPath with "/" results in a "//" prefix - for (String p : mParents) - mUploadPath += p + OCFile.PATH_SEPARATOR; - Log_OC.d(TAG, "Uploading file to dir " + mUploadPath); - - uploadFiles(); - - break; - default: - throw new IllegalArgumentException("Wrong element clicked"); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - Log_OC.i(TAG, "result received. req: " + requestCode + " res: " + resultCode); - if (requestCode == REQUEST_CODE_SETUP_ACCOUNT) { - dismissDialog(DIALOG_NO_ACCOUNT); - if (resultCode == RESULT_CANCELED) { - finish(); - } - Account[] accounts = mAccountManager.getAccountsByType(MainApp.getAuthTokenType()); - if (accounts.length == 0) { - showDialog(DIALOG_NO_ACCOUNT); - } else { - // there is no need for checking for is there more then one - // account at this point - // since account setup can set only one account at time - mAccount = accounts[0]; - populateDirectoryList(); - } - } - } - - private void populateDirectoryList() { - setContentView(R.layout.uploader_layout); - - String full_path = ""; - for (String a : mParents) - full_path += a + "/"; - - Log_OC.d(TAG, "Populating view with content of : " + full_path); - - mFile = mStorageManager.getFileByPath(full_path); - if (mFile != null) { - Vector files = mStorageManager.getDirectoryContent(mFile); - List> data = new LinkedList>(); - for (OCFile f : files) { - HashMap h = new HashMap(); - if (f.isDirectory()) { - h.put("dirname", f.getFileName()); - data.add(h); - } - } - SimpleAdapter sa = new SimpleAdapter(this, - data, - R.layout.uploader_list_item_layout, - new String[] {"dirname"}, - new int[] {R.id.textView1}); - setListAdapter(sa); - CustomButton btn = (CustomButton) findViewById(R.id.uploader_choose_folder); - btn.setOnClickListener(this); - getListView().setOnItemClickListener(this); - } - } - - private boolean prepareStreamsToUpload() { - if (getIntent().getAction().equals(Intent.ACTION_SEND)) { - mStreamsToUpload = new ArrayList(); - mStreamsToUpload.add(getIntent().getParcelableExtra(Intent.EXTRA_STREAM)); - } else if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) { - mStreamsToUpload = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM); - } - return (mStreamsToUpload != null && mStreamsToUpload.get(0) != null); - } - - public void uploadFiles() { - try { - //WebdavClient webdav = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext()); - - ArrayList local = new ArrayList(); - ArrayList remote = new ArrayList(); - - /* TODO - mCreateDir can never be true at this moment; we will replace wdc.createDirectory by CreateFolderOperation when that is fixed - WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext()); - // create last directory in path if necessary - if (mCreateDir) { - wdc.createDirectory(mUploadPath); - } - */ - - // this checks the mimeType - for (Parcelable mStream : mStreamsToUpload) { - - Uri uri = (Uri) mStream; - if (uri !=null) { - if (uri.getScheme().equals("content")) { - - String mimeType = getContentResolver().getType(uri); - - if (mimeType.contains("image")) { - String[] CONTENT_PROJECTION = { Images.Media.DATA, Images.Media.DISPLAY_NAME, Images.Media.MIME_TYPE, Images.Media.SIZE}; - Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null); - c.moveToFirst(); - int index = c.getColumnIndex(Images.Media.DATA); - String data = c.getString(index); - local.add(data); - remote.add(mUploadPath + c.getString(c.getColumnIndex(Images.Media.DISPLAY_NAME))); - - } - else if (mimeType.contains("video")) { - String[] CONTENT_PROJECTION = { Video.Media.DATA, Video.Media.DISPLAY_NAME, Video.Media.MIME_TYPE, Video.Media.SIZE, Video.Media.DATE_MODIFIED }; - Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null); - c.moveToFirst(); - int index = c.getColumnIndex(Video.Media.DATA); - String data = c.getString(index); - local.add(data); - remote.add(mUploadPath + c.getString(c.getColumnIndex(Video.Media.DISPLAY_NAME))); - - } - else if (mimeType.contains("audio")) { - String[] CONTENT_PROJECTION = { Audio.Media.DATA, Audio.Media.DISPLAY_NAME, Audio.Media.MIME_TYPE, Audio.Media.SIZE }; - Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null); - c.moveToFirst(); - int index = c.getColumnIndex(Audio.Media.DATA); - String data = c.getString(index); - local.add(data); - remote.add(mUploadPath + c.getString(c.getColumnIndex(Audio.Media.DISPLAY_NAME))); - - } - else { - String filePath = Uri.decode(uri.toString()).replace(uri.getScheme() + "://", ""); - // cut everything whats before mnt. It occured to me that sometimes apps send their name into the URI - if (filePath.contains("mnt")) { - String splitedFilePath[] = filePath.split("/mnt"); - filePath = splitedFilePath[1]; - } - final File file = new File(filePath); - local.add(file.getAbsolutePath()); - remote.add(mUploadPath + file.getName()); - } - - } else if (uri.getScheme().equals("file")) { - String filePath = Uri.decode(uri.toString()).replace(uri.getScheme() + "://", ""); - if (filePath.contains("mnt")) { - String splitedFilePath[] = filePath.split("/mnt"); - filePath = splitedFilePath[1]; - } - final File file = new File(filePath); - local.add(file.getAbsolutePath()); - remote.add(mUploadPath + file.getName()); - } - else { - throw new SecurityException(); - } - } - else { - 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, remote.toArray(new String[remote.size()])); - intent.putExtra(FileUploader.KEY_ACCOUNT, mAccount); - startService(intent); - finish(); - } - - } catch (SecurityException e) { - String message = String.format(getString(R.string.uploader_error_forbidden_content), getString(R.string.app_name)); - Toast.makeText(this, message, Toast.LENGTH_LONG).show(); - } - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/authentication/AccountAuthenticator.java b/src/de/mobilcom/debitel/cloud/android/authentication/AccountAuthenticator.java deleted file mode 100644 index 1c27e896..00000000 --- a/src/de/mobilcom/debitel/cloud/android/authentication/AccountAuthenticator.java +++ /dev/null @@ -1,358 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.authentication; - -import android.accounts.*; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.widget.Toast; - - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.R; - - -/** - * Authenticator for ownCloud accounts. - * - * Controller class accessed from the system AccountManager, providing integration of ownCloud accounts with the Android system. - * - * TODO - better separation in operations for OAuth-capable and regular ownCloud accounts. - * TODO - review completeness - * - * @author David A. Velasco - */ -public class AccountAuthenticator extends AbstractAccountAuthenticator { - - /** - * Is used by android system to assign accounts to authenticators. Should be - * used by application and all extensions. - */ - /* These constants are now in MainApp - public static final String ACCOUNT_TYPE = "owncloud"; - public static final String AUTHORITY = "org.owncloud"; - public static final String AUTH_TOKEN_TYPE = "org.owncloud"; - public static final String AUTH_TOKEN_TYPE_PASSWORD = "owncloud.password"; - public static final String AUTH_TOKEN_TYPE_ACCESS_TOKEN = "owncloud.oauth2.access_token"; - public static final String AUTH_TOKEN_TYPE_REFRESH_TOKEN = "owncloud.oauth2.refresh_token"; - public static final String AUTH_TOKEN_TYPE_SAML_WEB_SSO_SESSION_COOKIE = "owncloud.saml.web_sso.session_cookie"; - */ - public static final String KEY_AUTH_TOKEN_TYPE = "authTokenType"; - public static final String KEY_REQUIRED_FEATURES = "requiredFeatures"; - public static final String KEY_LOGIN_OPTIONS = "loginOptions"; - public static final String KEY_ACCOUNT = "account"; - - /** - * Value under this key should handle path to webdav php script. Will be - * removed and usage should be replaced by combining - * {@link de.mobilcom.debitel.cloud.android.authentication.AuthenticatorActivity.KEY_OC_BASE_URL} and - * {@link de.mobilcom.debitel.cloud.android.utils.OwnCloudVersion} - * - * @deprecated - */ - public static final String KEY_OC_URL = "oc_url"; - /** - * Version should be 3 numbers separated by dot so it can be parsed by - * {@link de.mobilcom.debitel.cloud.android.utils.OwnCloudVersion} - */ - public static final String KEY_OC_VERSION = "oc_version"; - /** - * Base url should point to owncloud installation without trailing / ie: - * http://server/path or https://owncloud.server - */ - public static final String KEY_OC_BASE_URL = "oc_base_url"; - /** - * Flag signaling if the ownCloud server can be accessed with OAuth2 access tokens. - */ - public static final String KEY_SUPPORTS_OAUTH2 = "oc_supports_oauth2"; - /** - * Flag signaling if the ownCloud server can be accessed with session cookies from SAML-based web single-sign-on. - */ - public static final String KEY_SUPPORTS_SAML_WEB_SSO = "oc_supports_saml_web_sso"; - - private static final String TAG = AccountAuthenticator.class.getSimpleName(); - - private Context mContext; - - private Handler mHandler; - - public AccountAuthenticator(Context context) { - super(context); - mContext = context; - mHandler = new Handler(); - } - - /** - * {@inheritDoc} - */ - @Override - public Bundle addAccount(AccountAuthenticatorResponse response, - String accountType, String authTokenType, - String[] requiredFeatures, Bundle options) - throws NetworkErrorException { - Log_OC.i(TAG, "Adding account with type " + accountType - + " and auth token " + authTokenType); - - final Bundle bundle = new Bundle(); - - AccountManager accountManager = AccountManager.get(mContext); - Account[] accounts = accountManager.getAccountsByType(MainApp.getAccountType()); - - if (mContext.getResources().getBoolean(R.bool.multiaccount_support) || accounts.length < 1) { - try { - validateAccountType(accountType); - } catch (AuthenticatorException e) { - Log_OC.e(TAG, "Failed to validate account type " + accountType + ": " - + e.getMessage()); - e.printStackTrace(); - return e.getFailureBundle(); - } - - final Intent intent = new Intent(mContext, AuthenticatorActivity.class); - intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); - intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); - intent.putExtra(KEY_REQUIRED_FEATURES, requiredFeatures); - intent.putExtra(KEY_LOGIN_OPTIONS, options); - intent.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_CREATE); - - setIntentFlags(intent); - - bundle.putParcelable(AccountManager.KEY_INTENT, intent); - - } else { - - // Return an error - bundle.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION); - final String message = String.format(mContext.getString(R.string.auth_unsupported_multiaccount), mContext.getString(R.string.app_name)); - bundle.putString(AccountManager.KEY_ERROR_MESSAGE, message); - - mHandler.post(new Runnable() { - - @Override - public void run() { - Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show(); - } - }); - - } - - return bundle; - } - - /** - * {@inheritDoc} - */ - @Override - public Bundle confirmCredentials(AccountAuthenticatorResponse response, - Account account, Bundle options) throws NetworkErrorException { - try { - validateAccountType(account.type); - } catch (AuthenticatorException e) { - Log_OC.e(TAG, "Failed to validate account type " + account.type + ": " - + e.getMessage()); - e.printStackTrace(); - return e.getFailureBundle(); - } - Intent intent = new Intent(mContext, AuthenticatorActivity.class); - intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, - response); - intent.putExtra(KEY_ACCOUNT, account); - intent.putExtra(KEY_LOGIN_OPTIONS, options); - - setIntentFlags(intent); - - Bundle resultBundle = new Bundle(); - resultBundle.putParcelable(AccountManager.KEY_INTENT, intent); - return resultBundle; - } - - @Override - public Bundle editProperties(AccountAuthenticatorResponse response, - String accountType) { - return null; - } - - /** - * {@inheritDoc} - */ - @Override - public Bundle getAuthToken(AccountAuthenticatorResponse response, - Account account, String authTokenType, Bundle options) - throws NetworkErrorException { - /// validate parameters - try { - validateAccountType(account.type); - validateAuthTokenType(authTokenType); - } catch (AuthenticatorException e) { - Log_OC.e(TAG, "Failed to validate account type " + account.type + ": " - + e.getMessage()); - e.printStackTrace(); - return e.getFailureBundle(); - } - - /// check if required token is stored - final AccountManager am = AccountManager.get(mContext); - String accessToken; - if (authTokenType.equals(MainApp.getAuthTokenTypePass())) { - accessToken = am.getPassword(account); - } else { - accessToken = am.peekAuthToken(account, authTokenType); - } - if (accessToken != null) { - final Bundle result = new Bundle(); - result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); - result.putString(AccountManager.KEY_ACCOUNT_TYPE, MainApp.getAccountType()); - result.putString(AccountManager.KEY_AUTHTOKEN, accessToken); - return result; - } - - /// if not stored, return Intent to access the AuthenticatorActivity and UPDATE the token for the account - final Intent intent = new Intent(mContext, AuthenticatorActivity.class); - intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); - intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); - intent.putExtra(KEY_LOGIN_OPTIONS, options); - intent.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, account); - intent.putExtra(AuthenticatorActivity.EXTRA_ENFORCED_UPDATE, true); - intent.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_TOKEN); - - - final Bundle bundle = new Bundle(); - bundle.putParcelable(AccountManager.KEY_INTENT, intent); - return bundle; - } - - @Override - public String getAuthTokenLabel(String authTokenType) { - return null; - } - - @Override - public Bundle hasFeatures(AccountAuthenticatorResponse response, - Account account, String[] features) throws NetworkErrorException { - final Bundle result = new Bundle(); - result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); - return result; - } - - @Override - public Bundle updateCredentials(AccountAuthenticatorResponse response, - Account account, String authTokenType, Bundle options) - throws NetworkErrorException { - final Intent intent = new Intent(mContext, AuthenticatorActivity.class); - intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, - response); - intent.putExtra(KEY_ACCOUNT, account); - intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); - intent.putExtra(KEY_LOGIN_OPTIONS, options); - setIntentFlags(intent); - - final Bundle bundle = new Bundle(); - bundle.putParcelable(AccountManager.KEY_INTENT, intent); - return bundle; - } - - @Override - public Bundle getAccountRemovalAllowed( - AccountAuthenticatorResponse response, Account account) - throws NetworkErrorException { - return super.getAccountRemovalAllowed(response, account); - } - - private void setIntentFlags(Intent intent) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - intent.addFlags(Intent.FLAG_FROM_BACKGROUND); - } - - private void validateAccountType(String type) - throws UnsupportedAccountTypeException { - if (!type.equals(MainApp.getAccountType())) { - throw new UnsupportedAccountTypeException(); - } - } - - private void validateAuthTokenType(String authTokenType) - throws UnsupportedAuthTokenTypeException { - if (!authTokenType.equals(MainApp.getAuthTokenType()) && - !authTokenType.equals(MainApp.getAuthTokenTypePass()) && - !authTokenType.equals(MainApp.getAuthTokenTypeAccessToken()) && - !authTokenType.equals(MainApp.getAuthTokenTypeRefreshToken()) && - !authTokenType.equals(MainApp.getAuthTokenTypeSamlSessionCookie())) { - throw new UnsupportedAuthTokenTypeException(); - } - } - - public static class AuthenticatorException extends Exception { - private static final long serialVersionUID = 1L; - private Bundle mFailureBundle; - - public AuthenticatorException(int code, String errorMsg) { - mFailureBundle = new Bundle(); - mFailureBundle.putInt(AccountManager.KEY_ERROR_CODE, code); - mFailureBundle - .putString(AccountManager.KEY_ERROR_MESSAGE, errorMsg); - } - - public Bundle getFailureBundle() { - return mFailureBundle; - } - } - - public static class UnsupportedAccountTypeException extends - AuthenticatorException { - private static final long serialVersionUID = 1L; - - public UnsupportedAccountTypeException() { - super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, - "Unsupported account type"); - } - } - - public static class UnsupportedAuthTokenTypeException extends - AuthenticatorException { - private static final long serialVersionUID = 1L; - - public UnsupportedAuthTokenTypeException() { - super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, - "Unsupported auth token type"); - } - } - - public static class UnsupportedFeaturesException extends - AuthenticatorException { - public static final long serialVersionUID = 1L; - - public UnsupportedFeaturesException() { - super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, - "Unsupported features"); - } - } - - public static class AccessDeniedException extends AuthenticatorException { - public AccessDeniedException(int code, String errorMsg) { - super(AccountManager.ERROR_CODE_INVALID_RESPONSE, "Access Denied"); - } - - private static final long serialVersionUID = 1L; - - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/authentication/AccountAuthenticatorActivity.java b/src/de/mobilcom/debitel/cloud/android/authentication/AccountAuthenticatorActivity.java deleted file mode 100644 index 9a6c9807..00000000 --- a/src/de/mobilcom/debitel/cloud/android/authentication/AccountAuthenticatorActivity.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.mobilcom.debitel.cloud.android.authentication; - -import android.accounts.AccountAuthenticatorResponse; -import android.accounts.AccountManager; -import android.os.Bundle; - -import com.actionbarsherlock.app.SherlockFragmentActivity; - - -/* - * Base class for implementing an Activity that is used to help implement an AbstractAccountAuthenticator. - * If the AbstractAccountAuthenticator needs to use an activity to handle the request then it can have the activity extend - * AccountAuthenticatorActivity. The AbstractAccountAuthenticator passes in the response to the intent using the following: - * intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); - * - * The activity then sets the result that is to be handed to the response via setAccountAuthenticatorResult(android.os.Bundle). - * This result will be sent as the result of the request when the activity finishes. If this is never set or if it is set to null - * then error AccountManager.ERROR_CODE_CANCELED will be called on the response. - */ - -public class AccountAuthenticatorActivity extends SherlockFragmentActivity { - - private AccountAuthenticatorResponse mAccountAuthenticatorResponse = null; - private Bundle mResultBundle = null; - - - /** - * Set the result that is to be sent as the result of the request that caused this Activity to be launched. - * If result is null or this method is never called then the request will be canceled. - * - * @param result this is returned as the result of the AbstractAccountAuthenticator request - */ - public final void setAccountAuthenticatorResult(Bundle result) { - mResultBundle = result; - } - - /** - * Retreives the AccountAuthenticatorResponse from either the intent of the icicle, if the - * icicle is non-zero. - * @param icicle the save instance data of this Activity, may be null - */ - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - mAccountAuthenticatorResponse = - getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE); - - if (mAccountAuthenticatorResponse != null) { - mAccountAuthenticatorResponse.onRequestContinued(); - } - } - - /** - * Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present. - */ - public void finish() { - if (mAccountAuthenticatorResponse != null) { - // send the result bundle back if set, otherwise send an error. - if (mResultBundle != null) { - mAccountAuthenticatorResponse.onResult(mResultBundle); - } else { - mAccountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, - "canceled"); - } - mAccountAuthenticatorResponse = null; - } - super.finish(); - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/authentication/AccountAuthenticatorService.java b/src/de/mobilcom/debitel/cloud/android/authentication/AccountAuthenticatorService.java deleted file mode 100644 index 9f00be13..00000000 --- a/src/de/mobilcom/debitel/cloud/android/authentication/AccountAuthenticatorService.java +++ /dev/null @@ -1,41 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.authentication; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; - -public class AccountAuthenticatorService extends Service { - - private AccountAuthenticator mAuthenticator; - //static final public String ACCOUNT_TYPE = "owncloud"; - - @Override - public void onCreate() { - super.onCreate(); - mAuthenticator = new AccountAuthenticator(this); - } - - @Override - public IBinder onBind(Intent intent) { - return mAuthenticator.getIBinder(); - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/authentication/AccountUtils.java b/src/de/mobilcom/debitel/cloud/android/authentication/AccountUtils.java deleted file mode 100644 index db0ea0d4..00000000 --- a/src/de/mobilcom/debitel/cloud/android/authentication/AccountUtils.java +++ /dev/null @@ -1,220 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.authentication; - -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.utils.OwnCloudVersion; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AccountsException; -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -public class AccountUtils { - public static final String WEBDAV_PATH_1_2 = "/webdav/owncloud.php"; - public static final String WEBDAV_PATH_2_0 = "/files/webdav.php"; - public static final String WEBDAV_PATH_4_0 = "/remote.php/webdav"; - private static final String ODAV_PATH = "/remote.php/odav"; - private static final String SAML_SSO_PATH = "/remote.php/webdav"; - public static final String CARDDAV_PATH_2_0 = "/apps/contacts/carddav.php"; - public static final String CARDDAV_PATH_4_0 = "/remote/carddav.php"; - public static final String STATUS_PATH = "/status.php"; - - /** - * Can be used to get the currently selected ownCloud {@link Account} in the - * application preferences. - * - * @param context The current application {@link Context} - * @return The ownCloud {@link Account} currently saved in preferences, or the first - * {@link Account} available, if valid (still registered in the system as ownCloud - * account). If none is available and valid, returns null. - */ - public static Account getCurrentOwnCloudAccount(Context context) { - Account[] ocAccounts = AccountManager.get(context).getAccountsByType( - MainApp.getAccountType()); - Account defaultAccount = null; - - SharedPreferences appPreferences = PreferenceManager - .getDefaultSharedPreferences(context); - String accountName = appPreferences - .getString("select_oc_account", null); - - // account validation: the saved account MUST be in the list of ownCloud Accounts known by the AccountManager - if (accountName != null) { - for (Account account : ocAccounts) { - if (account.name.equals(accountName)) { - defaultAccount = account; - break; - } - } - } - - if (defaultAccount == null && ocAccounts.length != 0) { - // take first account as fallback - defaultAccount = ocAccounts[0]; - } - - return defaultAccount; - } - - - public static boolean exists(Account account, Context context) { - Account[] ocAccounts = AccountManager.get(context).getAccountsByType( - MainApp.getAccountType()); - - if (account != null && account.name != null) { - for (Account ac : ocAccounts) { - if (ac.name.equals(account.name)) { - return true; - } - } - } - return false; - } - - - /** - * Checks, whether or not there are any ownCloud accounts setup. - * - * @return true, if there is at least one account. - */ - public static boolean accountsAreSetup(Context context) { - AccountManager accMan = AccountManager.get(context); - Account[] accounts = accMan - .getAccountsByType(MainApp.getAccountType()); - return accounts.length > 0; - } - - - public static boolean setCurrentOwnCloudAccount(Context context, String accountName) { - boolean result = false; - if (accountName != null) { - Account[] ocAccounts = AccountManager.get(context).getAccountsByType( - MainApp.getAccountType()); - boolean found = false; - for (Account account : ocAccounts) { - found = (account.name.equals(accountName)); - if (found) { - SharedPreferences.Editor appPrefs = PreferenceManager - .getDefaultSharedPreferences(context).edit(); - appPrefs.putString("select_oc_account", accountName); - - appPrefs.commit(); - result = true; - break; - } - } - } - return result; - } - - /** - * - * @param version version of owncloud - * @return webdav path for given OC version, null if OC version unknown - */ - public static String getWebdavPath(OwnCloudVersion version, boolean supportsOAuth, boolean supportsSamlSso) { - if (version != null) { - if (supportsOAuth) { - return ODAV_PATH; - } - if (supportsSamlSso) { - return SAML_SSO_PATH; - } - if (version.compareTo(OwnCloudVersion.owncloud_v4) >= 0) - return WEBDAV_PATH_4_0; - if (version.compareTo(OwnCloudVersion.owncloud_v3) >= 0 - || version.compareTo(OwnCloudVersion.owncloud_v2) >= 0) - return WEBDAV_PATH_2_0; - if (version.compareTo(OwnCloudVersion.owncloud_v1) >= 0) - return WEBDAV_PATH_1_2; - } - return null; - } - - /** - * Returns the proper URL path to access the WebDAV interface of an ownCloud server, - * according to its version and the authorization method used. - * - * @param version Version of ownCloud server. - * @param authTokenType Authorization token type, matching some of the AUTH_TOKEN_TYPE_* constants in {@link AccountAuthenticator}. - * @return WebDAV path for given OC version and authorization method, null if OC version is unknown. - */ - public static String getWebdavPath(OwnCloudVersion version, String authTokenType) { - if (version != null) { - if (MainApp.getAuthTokenTypeAccessToken().equals(authTokenType)) { - return ODAV_PATH; - } - if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(authTokenType)) { - return SAML_SSO_PATH; - } - if (version.compareTo(OwnCloudVersion.owncloud_v4) >= 0) - return WEBDAV_PATH_4_0; - if (version.compareTo(OwnCloudVersion.owncloud_v3) >= 0 - || version.compareTo(OwnCloudVersion.owncloud_v2) >= 0) - return WEBDAV_PATH_2_0; - if (version.compareTo(OwnCloudVersion.owncloud_v1) >= 0) - return WEBDAV_PATH_1_2; - } - return null; - } - - /** - * Constructs full url to host and webdav resource basing on host version - * @param context - * @param account - * @return url or null on failure - * @throws AccountNotFoundException When 'account' is unknown for the AccountManager - */ - public static String constructFullURLForAccount(Context context, Account account) throws AccountNotFoundException { - AccountManager ama = AccountManager.get(context); - String baseurl = ama.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL); - String strver = ama.getUserData(account, AccountAuthenticator.KEY_OC_VERSION); - boolean supportsOAuth = (ama.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null); - boolean supportsSamlSso = (ama.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_SAML_WEB_SSO) != null); - OwnCloudVersion ver = new OwnCloudVersion(strver); - String webdavpath = getWebdavPath(ver, supportsOAuth, supportsSamlSso); - - if (baseurl == null || webdavpath == null) - throw new AccountNotFoundException(account, "Account not found", null); - - return baseurl + webdavpath; - } - - - public static class AccountNotFoundException extends AccountsException { - - /** Generated - should be refreshed every time the class changes!! */ - private static final long serialVersionUID = -9013287181793186830L; - - private Account mFailedAccount; - - public AccountNotFoundException(Account failedAccount, String message, Throwable cause) { - super(message, cause); - mFailedAccount = failedAccount; - } - - public Account getFailedAccount() { - return mFailedAccount; - } - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/authentication/AuthenticatorActivity.java b/src/de/mobilcom/debitel/cloud/android/authentication/AuthenticatorActivity.java deleted file mode 100644 index 37097797..00000000 --- a/src/de/mobilcom/debitel/cloud/android/authentication/AuthenticatorActivity.java +++ /dev/null @@ -1,1672 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.authentication; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.ContentResolver; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.preference.PreferenceManager; -import android.support.v4.app.Fragment; -import android.text.Editable; -import android.text.InputType; -import android.text.TextWatcher; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnFocusChangeListener; -import android.view.View.OnTouchListener; -import android.view.Window; -import android.view.inputmethod.EditorInfo; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.TextView.OnEditorActionListener; - -import com.actionbarsherlock.app.SherlockDialogFragment; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.authentication.SsoWebViewClient.SsoWebViewClientListener; -import de.mobilcom.debitel.cloud.android.network.OwnCloudClientUtils; -import de.mobilcom.debitel.cloud.android.operations.ExistenceCheckOperation; -import de.mobilcom.debitel.cloud.android.operations.OAuth2GetAccessToken; -import de.mobilcom.debitel.cloud.android.operations.OnRemoteOperationListener; -import de.mobilcom.debitel.cloud.android.operations.OwnCloudServerCheckOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult.ResultCode; -import de.mobilcom.debitel.cloud.android.ui.CustomButton; -import de.mobilcom.debitel.cloud.android.ui.dialog.SamlWebViewDialog; -import de.mobilcom.debitel.cloud.android.ui.dialog.SslValidatorDialog; -import de.mobilcom.debitel.cloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener; -import de.mobilcom.debitel.cloud.android.utils.OwnCloudVersion; - -import eu.alefzero.webdav.WebdavClient; - -/** - * This Activity is used to add an ownCloud account to the App - * - * @author Bartek Przybylski - * @author David A. Velasco - */ -public class AuthenticatorActivity extends AccountAuthenticatorActivity -implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeListener, OnEditorActionListener, SsoWebViewClientListener{ - - private static final String TAG = AuthenticatorActivity.class.getSimpleName(); - - public static final String EXTRA_ACCOUNT = "ACCOUNT"; - public static final String EXTRA_USER_NAME = "USER_NAME"; - public static final String EXTRA_HOST_NAME = "HOST_NAME"; - public static final String EXTRA_ACTION = "ACTION"; - public static final String EXTRA_ENFORCED_UPDATE = "ENFORCE_UPDATE"; - - private static final String KEY_AUTH_MESSAGE_VISIBILITY = "AUTH_MESSAGE_VISIBILITY"; - private static final String KEY_AUTH_MESSAGE_TEXT = "AUTH_MESSAGE_TEXT"; - private static final String KEY_HOST_URL_TEXT = "HOST_URL_TEXT"; - private static final String KEY_OC_VERSION = "OC_VERSION"; - private static final String KEY_ACCOUNT = "ACCOUNT"; - private static final String KEY_SERVER_VALID = "SERVER_VALID"; - private static final String KEY_SERVER_CHECKED = "SERVER_CHECKED"; - private static final String KEY_SERVER_CHECK_IN_PROGRESS = "SERVER_CHECK_IN_PROGRESS"; - private static final String KEY_SERVER_STATUS_TEXT = "SERVER_STATUS_TEXT"; - private static final String KEY_SERVER_STATUS_ICON = "SERVER_STATUS_ICON"; - private static final String KEY_IS_SSL_CONN = "IS_SSL_CONN"; - private static final String KEY_PASSWORD_VISIBLE = "PASSWORD_VISIBLE"; - private static final String KEY_AUTH_STATUS_TEXT = "AUTH_STATUS_TEXT"; - private static final String KEY_AUTH_STATUS_ICON = "AUTH_STATUS_ICON"; - private static final String KEY_REFRESH_BUTTON_ENABLED = "KEY_REFRESH_BUTTON_ENABLED"; - - private static final String KEY_OC_USERNAME_EQUALS = "oc_username="; - - private static final String AUTH_ON = "on"; - private static final String AUTH_OFF = "off"; - private static final String AUTH_OPTIONAL = "optional"; - - private static final int DIALOG_LOGIN_PROGRESS = 0; - private static final int DIALOG_SSL_VALIDATOR = 1; - private static final int DIALOG_CERT_NOT_SAVED = 2; - private static final int DIALOG_OAUTH2_LOGIN_PROGRESS = 3; - - public static final byte ACTION_CREATE = 0; - public static final byte ACTION_UPDATE_TOKEN = 1; - - private static final String TAG_SAML_DIALOG = "samlWebViewDialog"; - - private String mHostBaseUrl; - private OwnCloudVersion mDiscoveredVersion; - - private String mAuthMessageText; - private int mAuthMessageVisibility, mServerStatusText, mServerStatusIcon; - private boolean mServerIsChecked, mServerIsValid, mIsSslConn; - private int mAuthStatusText, mAuthStatusIcon; - private TextView mAuthStatusLayout; - - private final Handler mHandler = new Handler(); - private Thread mOperationThread; - private OwnCloudServerCheckOperation mOcServerChkOperation; - private ExistenceCheckOperation mAuthCheckOperation; - private RemoteOperationResult mLastSslUntrustedServerResult; - - private Uri mNewCapturedUriFromOAuth2Redirection; - - private AccountManager mAccountMgr; - private boolean mJustCreated; - private byte mAction; - private Account mAccount; - - private TextView mAuthMessage; - - private EditText mHostUrlInput; - private boolean mHostUrlInputEnabled; - private View mRefreshButton; - - private String mAuthTokenType; - - private EditText mUsernameInput; - private EditText mPasswordInput; - - private CheckBox mOAuth2Check; - - private TextView mOAuthAuthEndpointText; - private TextView mOAuthTokenEndpointText; - - private SamlWebViewDialog mSamlDialog; - - private View mOkButton; - - private String mAuthToken; - - private boolean mResumed; // Control if activity is resumed - - - /** - * {@inheritDoc} - * - * IMPORTANT ENTRY POINT 1: activity is shown to the user - */ - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().requestFeature(Window.FEATURE_NO_TITLE); - - /// set view and get references to view elements - setContentView(R.layout.account_setup); - mAuthMessage = (TextView) findViewById(R.id.auth_message); - mHostUrlInput = (EditText) findViewById(R.id.hostUrlInput); - mHostUrlInput.setText(getString(R.string.server_url)); // valid although R.string.server_url is an empty string - mUsernameInput = (EditText) findViewById(R.id.account_username); - mPasswordInput = (EditText) findViewById(R.id.account_password); - mOAuthAuthEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_1); - mOAuthTokenEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_2); - mOAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check); - mOkButton = (CustomButton) findViewById(R.id.buttonOK); - mAuthStatusLayout = (TextView) findViewById(R.id.auth_status_text); - - /// set Host Url Input Enabled - mHostUrlInputEnabled = getResources().getBoolean(R.bool.show_server_url_input); - - - /// complete label for 'register account' button - Button b = (Button) findViewById(R.id.account_register); - if (b != null) { - b.setText(String.format(getString(R.string.auth_register), getString(R.string.app_name))); - } - -// /// complete background of 'OK' button -// boolean customButtons = getResources().getBoolean(R.bool.custom_buttons); -// if (customButtons) -// mOkButton.setBackgroundResource(R.drawable.btn_default); - - /// initialization - mAccountMgr = AccountManager.get(this); - mNewCapturedUriFromOAuth2Redirection = null; - mAction = getIntent().getByteExtra(EXTRA_ACTION, ACTION_CREATE); - mAccount = null; - mHostBaseUrl = ""; - boolean refreshButtonEnabled = false; - - // URL input configuration applied - if (!mHostUrlInputEnabled) - { - findViewById(R.id.hostUrlFrame).setVisibility(View.GONE); - mRefreshButton = findViewById(R.id.centeredRefreshButton); - - } else { - mRefreshButton = findViewById(R.id.embeddedRefreshButton); - } - - if (savedInstanceState == null) { - mResumed = false; - /// connection state and info - mAuthMessageVisibility = View.GONE; - mServerStatusText = mServerStatusIcon = 0; - mServerIsValid = false; - mServerIsChecked = false; - mIsSslConn = false; - mAuthStatusText = mAuthStatusIcon = 0; - - /// retrieve extras from intent - mAccount = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT); - if (mAccount != null) { - String ocVersion = mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION); - if (ocVersion != null) { - mDiscoveredVersion = new OwnCloudVersion(ocVersion); - } - mHostBaseUrl = normalizeUrl(mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL)); - mHostUrlInput.setText(mHostBaseUrl); - String userName = mAccount.name.substring(0, mAccount.name.lastIndexOf('@')); - mUsernameInput.setText(userName); - } - initAuthorizationMethod(); // checks intent and setup.xml to determine mCurrentAuthorizationMethod - mJustCreated = true; - - if (mAction == ACTION_UPDATE_TOKEN || !mHostUrlInputEnabled) { - checkOcServer(); - } - - } else { - mResumed = true; - /// connection state and info - mAuthMessageVisibility = savedInstanceState.getInt(KEY_AUTH_MESSAGE_VISIBILITY); - mAuthMessageText = savedInstanceState.getString(KEY_AUTH_MESSAGE_TEXT); - mServerIsValid = savedInstanceState.getBoolean(KEY_SERVER_VALID); - mServerIsChecked = savedInstanceState.getBoolean(KEY_SERVER_CHECKED); - mServerStatusText = savedInstanceState.getInt(KEY_SERVER_STATUS_TEXT); - mServerStatusIcon = savedInstanceState.getInt(KEY_SERVER_STATUS_ICON); - mIsSslConn = savedInstanceState.getBoolean(KEY_IS_SSL_CONN); - mAuthStatusText = savedInstanceState.getInt(KEY_AUTH_STATUS_TEXT); - mAuthStatusIcon = savedInstanceState.getInt(KEY_AUTH_STATUS_ICON); - if (savedInstanceState.getBoolean(KEY_PASSWORD_VISIBLE, false)) { - showPassword(); - } - - /// server data - String ocVersion = savedInstanceState.getString(KEY_OC_VERSION); - if (ocVersion != null) { - mDiscoveredVersion = new OwnCloudVersion(ocVersion); - } - mHostBaseUrl = savedInstanceState.getString(KEY_HOST_URL_TEXT); - - // account data, if updating - mAccount = savedInstanceState.getParcelable(KEY_ACCOUNT); - mAuthTokenType = savedInstanceState.getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE); - if (mAuthTokenType == null) { - mAuthTokenType = MainApp.getAuthTokenTypePass(); - - } - - // check if server check was interrupted by a configuration change - if (savedInstanceState.getBoolean(KEY_SERVER_CHECK_IN_PROGRESS, false)) { - checkOcServer(); - } - - // refresh button enabled - refreshButtonEnabled = savedInstanceState.getBoolean(KEY_REFRESH_BUTTON_ENABLED); - - - } - - if (mAuthMessageVisibility== View.VISIBLE) { - showAuthMessage(mAuthMessageText); - } - else { - hideAuthMessage(); - } - adaptViewAccordingToAuthenticationMethod(); - showServerStatus(); - showAuthStatus(); - - if (mAction == ACTION_UPDATE_TOKEN) { - /// lock things that should not change - mHostUrlInput.setEnabled(false); - mHostUrlInput.setFocusable(false); - mUsernameInput.setEnabled(false); - mUsernameInput.setFocusable(false); - mOAuth2Check.setVisibility(View.GONE); - } - - //if (mServerIsChecked && !mServerIsValid && mRefreshButtonEnabled) showRefreshButton(); - if (mServerIsChecked && !mServerIsValid && refreshButtonEnabled) showRefreshButton(); - mOkButton.setEnabled(mServerIsValid); // state not automatically recovered in configuration changes - - if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType) || - !AUTH_OPTIONAL.equals(getString(R.string.auth_method_oauth2))) { - mOAuth2Check.setVisibility(View.GONE); - } - - mPasswordInput.setText(""); // clean password to avoid social hacking (disadvantage: password in removed if the device is turned aside) - - /// bind view elements to listeners and other friends - mHostUrlInput.setOnFocusChangeListener(this); - mHostUrlInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); - mHostUrlInput.setOnEditorActionListener(this); - mHostUrlInput.addTextChangedListener(new TextWatcher() { - - @Override - public void afterTextChanged(Editable s) { - if (!mHostBaseUrl.equals(normalizeUrl(mHostUrlInput.getText().toString()))) { - mOkButton.setEnabled(false); - } - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - if (!mResumed) { - mAuthStatusIcon = 0; - mAuthStatusText = 0; - showAuthStatus(); - } - mResumed = false; - } - }); - - mPasswordInput.setOnFocusChangeListener(this); - mPasswordInput.setImeOptions(EditorInfo.IME_ACTION_DONE); - mPasswordInput.setOnEditorActionListener(this); - mPasswordInput.setOnTouchListener(new RightDrawableOnTouchListener() { - @Override - public boolean onDrawableTouch(final MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_UP) { - AuthenticatorActivity.this.onViewPasswordClick(); - } - return true; - } - }); - - findViewById(R.id.scroll).setOnTouchListener(new OnTouchListener() { - @Override - public boolean onTouch(View view, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType) && - mHostUrlInput.hasFocus()) { - checkOcServer(); - } - } - return false; - } - }); - } - - - - private void initAuthorizationMethod() { - boolean oAuthRequired = false; - boolean samlWebSsoRequired = false; - - mAuthTokenType = getIntent().getExtras().getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE); - mAccount = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT); - - // TODO could be a good moment to validate the received token type, if not null - - if (mAuthTokenType == null) { - if (mAccount != null) { - /// same authentication method than the one used to create the account to update - oAuthRequired = (mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null); - samlWebSsoRequired = (mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_SAML_WEB_SSO) != null); - - } else { - /// use the one set in setup.xml - oAuthRequired = AUTH_ON.equals(getString(R.string.auth_method_oauth2)); - samlWebSsoRequired = AUTH_ON.equals(getString(R.string.auth_method_saml_web_sso)); - } - if (oAuthRequired) { - mAuthTokenType = MainApp.getAuthTokenTypeAccessToken(); - } else if (samlWebSsoRequired) { - mAuthTokenType = MainApp.getAuthTokenTypeSamlSessionCookie(); - } else { - mAuthTokenType = MainApp.getAuthTokenTypePass(); - } - } - - if (mAccount != null) { - String userName = mAccount.name.substring(0, mAccount.name.lastIndexOf('@')); - mUsernameInput.setText(userName); - } - - mOAuth2Check.setChecked(MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType)); - - } - - /** - * Saves relevant state before {@link #onPause()} - * - * Do NOT save {@link #mNewCapturedUriFromOAuth2Redirection}; it keeps a temporal flag, intended to defer the - * processing of the redirection caught in {@link #onNewIntent(Intent)} until {@link #onResume()} - * - * See {@link #loadSavedInstanceState(Bundle)} - */ - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - /// connection state and info - outState.putInt(KEY_AUTH_MESSAGE_VISIBILITY, mAuthMessage.getVisibility()); - outState.putString(KEY_AUTH_MESSAGE_TEXT, mAuthMessage.getText().toString()); - outState.putInt(KEY_SERVER_STATUS_TEXT, mServerStatusText); - outState.putInt(KEY_SERVER_STATUS_ICON, mServerStatusIcon); - outState.putBoolean(KEY_SERVER_VALID, mServerIsValid); - outState.putBoolean(KEY_SERVER_CHECKED, mServerIsChecked); - outState.putBoolean(KEY_SERVER_CHECK_IN_PROGRESS, (!mServerIsValid && mOcServerChkOperation != null)); - outState.putBoolean(KEY_IS_SSL_CONN, mIsSslConn); - outState.putBoolean(KEY_PASSWORD_VISIBLE, isPasswordVisible()); - outState.putInt(KEY_AUTH_STATUS_ICON, mAuthStatusIcon); - outState.putInt(KEY_AUTH_STATUS_TEXT, mAuthStatusText); - - /// server data - if (mDiscoveredVersion != null) { - outState.putString(KEY_OC_VERSION, mDiscoveredVersion.toString()); - } - outState.putString(KEY_HOST_URL_TEXT, mHostBaseUrl); - - /// account data, if updating - if (mAccount != null) { - outState.putParcelable(KEY_ACCOUNT, mAccount); - } - outState.putString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE, mAuthTokenType); - - // refresh button enabled - outState.putBoolean(KEY_REFRESH_BUTTON_ENABLED, (mRefreshButton.getVisibility() == View.VISIBLE)); - - - } - - - /** - * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION request - * is caught here. - * - * To make this possible, this activity needs to be qualified with android:launchMode = "singleTask" in the - * AndroidManifest.xml file. - */ - @Override - protected void onNewIntent (Intent intent) { - Log_OC.d(TAG, "onNewIntent()"); - Uri data = intent.getData(); - if (data != null && data.toString().startsWith(getString(R.string.oauth2_redirect_uri))) { - mNewCapturedUriFromOAuth2Redirection = data; - } - } - - - /** - * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION, and - * deferred in {@link #onNewIntent(Intent)}, is processed here. - */ - @Override - protected void onResume() { - super.onResume(); - if (mAction == ACTION_UPDATE_TOKEN && mJustCreated && getIntent().getBooleanExtra(EXTRA_ENFORCED_UPDATE, false)) { - if (MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType)) { - //Toast.makeText(this, R.string.auth_expired_oauth_token_toast, Toast.LENGTH_LONG).show(); - showAuthMessage(getString(R.string.auth_expired_oauth_token_toast)); - } else if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType)) { - //Toast.makeText(this, R.string.auth_expired_saml_sso_token_toast, Toast.LENGTH_LONG).show(); - showAuthMessage(getString(R.string.auth_expired_saml_sso_token_toast)); - } else { - //Toast.makeText(this, R.string.auth_expired_basic_auth_toast, Toast.LENGTH_LONG).show(); - showAuthMessage(getString(R.string.auth_expired_basic_auth_toast)); - } - } - - if (mNewCapturedUriFromOAuth2Redirection != null) { - getOAuth2AccessTokenFromCapturedRedirection(); - } - - mJustCreated = false; - - } - - - /** - * Parses the redirection with the response to the GET AUTHORIZATION request to the - * oAuth server and requests for the access token (GET ACCESS TOKEN) - */ - private void getOAuth2AccessTokenFromCapturedRedirection() { - /// Parse data from OAuth redirection - String queryParameters = mNewCapturedUriFromOAuth2Redirection.getQuery(); - mNewCapturedUriFromOAuth2Redirection = null; - - /// Showing the dialog with instructions for the user. - showDialog(DIALOG_OAUTH2_LOGIN_PROGRESS); - - /// GET ACCESS TOKEN to the oAuth server - RemoteOperation operation = new OAuth2GetAccessToken( getString(R.string.oauth2_client_id), - getString(R.string.oauth2_redirect_uri), - getString(R.string.oauth2_grant_type), - queryParameters); - //WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(getString(R.string.oauth2_url_endpoint_access)), getApplicationContext()); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mOAuthTokenEndpointText.getText().toString().trim()), getApplicationContext(), true); - operation.execute(client, this, mHandler); - } - - - - /** - * Handles the change of focus on the text inputs for the server URL and the password - */ - public void onFocusChange(View view, boolean hasFocus) { - if (view.getId() == R.id.hostUrlInput) { - if (!hasFocus) { - onUrlInputFocusLost((TextView) view); - } - else { - hideRefreshButton(); - } - - } else if (view.getId() == R.id.account_password) { - onPasswordFocusChanged((TextView) view, hasFocus); - } - } - - - /** - * Handles changes in focus on the text input for the server URL. - * - * IMPORTANT ENTRY POINT 2: When (!hasFocus), user wrote the server URL and changed to - * other field. The operation to check the existence of the server in the entered URL is - * started. - * - * When hasFocus: user 'comes back' to write again the server URL. - * - * @param hostInput TextView with the URL input field receiving the change of focus. - */ - private void onUrlInputFocusLost(TextView hostInput) { - if (!mHostBaseUrl.equals(normalizeUrl(mHostUrlInput.getText().toString()))) { - checkOcServer(); - } else { - mOkButton.setEnabled(mServerIsValid); - if (!mServerIsValid) { - showRefreshButton(); - } - } - } - - - private void checkOcServer() { - String uri = trimUrlWebdav(mHostUrlInput.getText().toString().trim()); - - if (!mHostUrlInputEnabled){ - uri = getString(R.string.server_url); - } - - mServerIsValid = false; - mServerIsChecked = false; - mOkButton.setEnabled(false); - mDiscoveredVersion = null; - hideRefreshButton(); - if (uri.length() != 0) { - mServerStatusText = R.string.auth_testing_connection; - mServerStatusIcon = R.drawable.progress_small; - showServerStatus(); - mOcServerChkOperation = new OwnCloudServerCheckOperation(uri, this); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(uri), this, true); - mOperationThread = mOcServerChkOperation.execute(client, this, mHandler); - } else { - mServerStatusText = 0; - mServerStatusIcon = 0; - showServerStatus(); - } - } - - - /** - * Handles changes in focus on the text input for the password (basic authorization). - * - * When (hasFocus), the button to toggle password visibility is shown. - * - * When (!hasFocus), the button is made invisible and the password is hidden. - * - * @param passwordInput TextView with the password input field receiving the change of focus. - * @param hasFocus 'True' if focus is received, 'false' if is lost - */ - private void onPasswordFocusChanged(TextView passwordInput, boolean hasFocus) { - if (hasFocus) { - showViewPasswordButton(); - } else { - hidePassword(); - hidePasswordButton(); - } - } - - - private void showViewPasswordButton() { - //int drawable = android.R.drawable.ic_menu_view; - int drawable = R.drawable.ic_view; - if (isPasswordVisible()) { - //drawable = android.R.drawable.ic_secure; - drawable = R.drawable.ic_hide; - } - mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, drawable, 0); - } - - private boolean isPasswordVisible() { - return ((mPasswordInput.getInputType() & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); - } - - private void hidePasswordButton() { - mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - } - - private void showPassword() { - mPasswordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); - showViewPasswordButton(); - } - - private void hidePassword() { - mPasswordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); - showViewPasswordButton(); - } - - - /** - * Cancels the authenticator activity - * - * IMPORTANT ENTRY POINT 3: Never underestimate the importance of cancellation - * - * This method is bound in the layout/acceoun_setup.xml resource file. - * - * @param view Cancel button - */ - public void onCancelClick(View view) { - setResult(RESULT_CANCELED); // TODO review how is this related to AccountAuthenticator (debugging) - finish(); - } - - - - /** - * Checks the credentials of the user in the root of the ownCloud server - * before creating a new local account. - * - * For basic authorization, a check of existence of the root folder is - * performed. - * - * For OAuth, starts the flow to get an access token; the credentials test - * is postponed until it is available. - * - * IMPORTANT ENTRY POINT 4 - * - * @param view OK button - */ - public void onOkClick(View view) { - // this check should be unnecessary - if (mDiscoveredVersion == null || !mDiscoveredVersion.isVersionValid() || mHostBaseUrl == null || mHostBaseUrl.length() == 0) { - mServerStatusIcon = R.drawable.common_error; - mServerStatusText = R.string.auth_wtf_reenter_URL; - showServerStatus(); - mOkButton.setEnabled(false); - Log_OC.wtf(TAG, "The user was allowed to click 'connect' to an unchecked server!!"); - return; - } - - if (MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType)) { - startOauthorization(); - } else if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType)) { - startSamlBasedFederatedSingleSignOnAuthorization(); - } else { - checkBasicAuthorization(); - } - } - - - /** - * Tests the credentials entered by the user performing a check of existence on - * the root folder of the ownCloud server. - */ - private void checkBasicAuthorization() { - /// get the path to the root folder through WebDAV from the version server - String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, mAuthTokenType); - - /// get basic credentials entered by user - String username = mUsernameInput.getText().toString(); - String password = mPasswordInput.getText().toString(); - - /// be gentle with the user - showDialog(DIALOG_LOGIN_PROGRESS); - - /// test credentials accessing the root folder - mAuthCheckOperation = new ExistenceCheckOperation("", this, false); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, true); - client.setBasicCredentials(username, password); - mOperationThread = mAuthCheckOperation.execute(client, this, mHandler); - } - - - /** - * Starts the OAuth 'grant type' flow to get an access token, with - * a GET AUTHORIZATION request to the BUILT-IN authorization server. - */ - private void startOauthorization() { - // be gentle with the user - mAuthStatusIcon = R.drawable.progress_small; - mAuthStatusText = R.string.oauth_login_connection; - showAuthStatus(); - - - // GET AUTHORIZATION request - //Uri uri = Uri.parse(getString(R.string.oauth2_url_endpoint_auth)); - Uri uri = Uri.parse(mOAuthAuthEndpointText.getText().toString().trim()); - Uri.Builder uriBuilder = uri.buildUpon(); - uriBuilder.appendQueryParameter(OAuth2Constants.KEY_RESPONSE_TYPE, getString(R.string.oauth2_response_type)); - uriBuilder.appendQueryParameter(OAuth2Constants.KEY_REDIRECT_URI, getString(R.string.oauth2_redirect_uri)); - uriBuilder.appendQueryParameter(OAuth2Constants.KEY_CLIENT_ID, getString(R.string.oauth2_client_id)); - uriBuilder.appendQueryParameter(OAuth2Constants.KEY_SCOPE, getString(R.string.oauth2_scope)); - //uriBuilder.appendQueryParameter(OAuth2Constants.KEY_STATE, whateverwewant); - uri = uriBuilder.build(); - Log_OC.d(TAG, "Starting browser to view " + uri.toString()); - Intent i = new Intent(Intent.ACTION_VIEW, uri); - startActivity(i); - } - - - /** - * Starts the Web Single Sign On flow to get access to the root folder - * in the server. - */ - private void startSamlBasedFederatedSingleSignOnAuthorization() { - // be gentle with the user - mAuthStatusIcon = R.drawable.progress_small; - mAuthStatusText = R.string.auth_connecting_auth_server; - showAuthStatus(); - showDialog(DIALOG_LOGIN_PROGRESS); - - /// get the path to the root folder through WebDAV from the version server - String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, mAuthTokenType); - - /// test credentials accessing the root folder - mAuthCheckOperation = new ExistenceCheckOperation("", this, false); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, false); - mOperationThread = mAuthCheckOperation.execute(client, this, mHandler); - - } - - /** - * Callback method invoked when a RemoteOperation executed by this Activity finishes. - * - * Dispatches the operation flow to the right method. - */ - @Override - public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { - - if (operation instanceof OwnCloudServerCheckOperation) { - onOcServerCheckFinish((OwnCloudServerCheckOperation) operation, result); - - } else if (operation instanceof OAuth2GetAccessToken) { - onGetOAuthAccessTokenFinish((OAuth2GetAccessToken)operation, result); - - } else if (operation instanceof ExistenceCheckOperation) { - if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType)) { - onSamlBasedFederatedSingleSignOnAuthorizationStart(operation, result); - - } else { - onAuthorizationCheckFinish((ExistenceCheckOperation)operation, result); - } - } - } - - - private void onSamlBasedFederatedSingleSignOnAuthorizationStart(RemoteOperation operation, RemoteOperationResult result) { - try { - dismissDialog(DIALOG_LOGIN_PROGRESS); - } catch (IllegalArgumentException e) { - // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens - } - - //if (result.isTemporalRedirection() && result.isIdPRedirection()) { - if (result.isIdPRedirection()) { - String url = result.getRedirectedLocation(); - String targetUrl = mHostBaseUrl + AccountUtils.getWebdavPath(mDiscoveredVersion, mAuthTokenType); - - // Show dialog - mSamlDialog = SamlWebViewDialog.newInstance(url, targetUrl); - mSamlDialog.show(getSupportFragmentManager(), TAG_SAML_DIALOG); - - mAuthStatusIcon = 0; - mAuthStatusText = 0; - - } else { - mAuthStatusIcon = R.drawable.common_error; - mAuthStatusText = R.string.auth_unsupported_auth_method; - - } - showAuthStatus(); - } - - - /** - * Processes the result of the server check performed when the user finishes the enter of the - * server URL. - * - * @param operation Server check performed. - * @param result Result of the check. - */ - private void onOcServerCheckFinish(OwnCloudServerCheckOperation operation, RemoteOperationResult result) { - if (operation.equals(mOcServerChkOperation)) { - /// save result state - mServerIsChecked = true; - mServerIsValid = result.isSuccess(); - mIsSslConn = (result.getCode() == ResultCode.OK_SSL); - mOcServerChkOperation = null; - - /// update status icon and text - if (mServerIsValid) { - hideRefreshButton(); - } else { - showRefreshButton(); - } - updateServerStatusIconAndText(result); - showServerStatus(); - - /// very special case (TODO: move to a common place for all the remote operations) - if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) { - mLastSslUntrustedServerResult = result; - showDialog(DIALOG_SSL_VALIDATOR); - } - - /// retrieve discovered version and normalize server URL - mDiscoveredVersion = operation.getDiscoveredVersion(); - mHostBaseUrl = normalizeUrl(mHostUrlInput.getText().toString()); - - /// allow or not the user try to access the server - mOkButton.setEnabled(mServerIsValid); - - } // else nothing ; only the last check operation is considered; - // multiple can be triggered if the user amends a URL before a previous check can be triggered - } - - - private String normalizeUrl(String url) { - if (url != null && url.length() > 0) { - url = url.trim(); - if (!url.toLowerCase().startsWith("http://") && - !url.toLowerCase().startsWith("https://")) { - if (mIsSslConn) { - url = "https://" + url; - } else { - url = "http://" + url; - } - } - - // OC-208: Add suffix remote.php/webdav to normalize (OC-34) - url = trimUrlWebdav(url); - - if (url.endsWith("/")) { - url = url.substring(0, url.length() - 1); - } - - } - return (url != null ? url : ""); - } - - - private String trimUrlWebdav(String url){ - if(url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_4_0)){ - url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_4_0.length()); - } else if(url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_2_0)){ - url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_2_0.length()); - } else if (url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_1_2)){ - url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_1_2.length()); - } - return (url != null ? url : ""); - } - - - /** - * Chooses the right icon and text to show to the user for the received operation result. - * - * @param result Result of a remote operation performed in this activity - */ - private void updateServerStatusIconAndText(RemoteOperationResult result) { - mServerStatusIcon = R.drawable.common_error; // the most common case in the switch below - - switch (result.getCode()) { - case OK_SSL: - mServerStatusIcon = android.R.drawable.ic_secure; - mServerStatusText = R.string.auth_secure_connection; - break; - - case OK_NO_SSL: - case OK: - if (mHostUrlInput.getText().toString().trim().toLowerCase().startsWith("http://") ) { - mServerStatusText = R.string.auth_connection_established; - mServerStatusIcon = R.drawable.ic_ok; - } else { - mServerStatusText = R.string.auth_nossl_plain_ok_title; - mServerStatusIcon = android.R.drawable.ic_partial_secure; - } - break; - - case NO_NETWORK_CONNECTION: - mServerStatusIcon = R.drawable.no_network; - mServerStatusText = R.string.auth_no_net_conn_title; - break; - - case SSL_RECOVERABLE_PEER_UNVERIFIED: - mServerStatusText = R.string.auth_ssl_unverified_server_title; - break; - case BAD_OC_VERSION: - mServerStatusText = R.string.auth_bad_oc_version_title; - break; - case WRONG_CONNECTION: - mServerStatusText = R.string.auth_wrong_connection_title; - break; - case TIMEOUT: - mServerStatusText = R.string.auth_timeout_title; - break; - case INCORRECT_ADDRESS: - mServerStatusText = R.string.auth_incorrect_address_title; - break; - case SSL_ERROR: - mServerStatusText = R.string.auth_ssl_general_error_title; - break; - case UNAUTHORIZED: - mServerStatusText = R.string.auth_unauthorized; - break; - case HOST_NOT_AVAILABLE: - mServerStatusText = R.string.auth_unknown_host_title; - break; - case INSTANCE_NOT_CONFIGURED: - mServerStatusText = R.string.auth_not_configured_title; - break; - case FILE_NOT_FOUND: - mServerStatusText = R.string.auth_incorrect_path_title; - break; - case OAUTH2_ERROR: - mServerStatusText = R.string.auth_oauth_error; - break; - case OAUTH2_ERROR_ACCESS_DENIED: - mServerStatusText = R.string.auth_oauth_error_access_denied; - break; - case UNHANDLED_HTTP_CODE: - case UNKNOWN_ERROR: - mServerStatusText = R.string.auth_unknown_error_title; - break; - default: - mServerStatusText = 0; - mServerStatusIcon = 0; - } - } - - - /** - * Chooses the right icon and text to show to the user for the received operation result. - * - * @param result Result of a remote operation performed in this activity - */ - private void updateAuthStatusIconAndText(RemoteOperationResult result) { - mAuthStatusIcon = R.drawable.common_error; // the most common case in the switch below - - switch (result.getCode()) { - case OK_SSL: - mAuthStatusIcon = android.R.drawable.ic_secure; - mAuthStatusText = R.string.auth_secure_connection; - break; - - case OK_NO_SSL: - case OK: - if (mHostUrlInput.getText().toString().trim().toLowerCase().startsWith("http://") ) { - mAuthStatusText = R.string.auth_connection_established; - mAuthStatusIcon = R.drawable.ic_ok; - } else { - mAuthStatusText = R.string.auth_nossl_plain_ok_title; - mAuthStatusIcon = android.R.drawable.ic_partial_secure; - } - break; - - case NO_NETWORK_CONNECTION: - mAuthStatusIcon = R.drawable.no_network; - mAuthStatusText = R.string.auth_no_net_conn_title; - break; - - case SSL_RECOVERABLE_PEER_UNVERIFIED: - mAuthStatusText = R.string.auth_ssl_unverified_server_title; - break; - case BAD_OC_VERSION: - mAuthStatusText = R.string.auth_bad_oc_version_title; - break; - case WRONG_CONNECTION: - mAuthStatusText = R.string.auth_wrong_connection_title; - break; - case TIMEOUT: - mAuthStatusText = R.string.auth_timeout_title; - break; - case INCORRECT_ADDRESS: - mAuthStatusText = R.string.auth_incorrect_address_title; - break; - case SSL_ERROR: - mAuthStatusText = R.string.auth_ssl_general_error_title; - break; - case UNAUTHORIZED: - mAuthStatusText = R.string.auth_unauthorized; - break; - case HOST_NOT_AVAILABLE: - mAuthStatusText = R.string.auth_unknown_host_title; - break; - case INSTANCE_NOT_CONFIGURED: - mAuthStatusText = R.string.auth_not_configured_title; - break; - case FILE_NOT_FOUND: - mAuthStatusText = R.string.auth_incorrect_path_title; - break; - case OAUTH2_ERROR: - mAuthStatusText = R.string.auth_oauth_error; - break; - case OAUTH2_ERROR_ACCESS_DENIED: - mAuthStatusText = R.string.auth_oauth_error_access_denied; - break; - case ACCOUNT_NOT_NEW: - mAuthStatusText = R.string.auth_account_not_new; - break; - case ACCOUNT_NOT_THE_SAME: - mAuthStatusText = R.string.auth_account_not_the_same; - break; - case UNHANDLED_HTTP_CODE: - case UNKNOWN_ERROR: - mAuthStatusText = R.string.auth_unknown_error_title; - break; - default: - mAuthStatusText = 0; - mAuthStatusIcon = 0; - } - } - - - /** - * Processes the result of the request for and access token send - * to an OAuth authorization server. - * - * @param operation Operation performed requesting the access token. - * @param result Result of the operation. - */ - private void onGetOAuthAccessTokenFinish(OAuth2GetAccessToken operation, RemoteOperationResult result) { - try { - dismissDialog(DIALOG_OAUTH2_LOGIN_PROGRESS); - } catch (IllegalArgumentException e) { - // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens - } - - String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, mAuthTokenType); - if (result.isSuccess() && webdav_path != null) { - /// be gentle with the user - showDialog(DIALOG_LOGIN_PROGRESS); - - /// time to test the retrieved access token on the ownCloud server - mAuthToken = ((OAuth2GetAccessToken)operation).getResultTokenMap().get(OAuth2Constants.KEY_ACCESS_TOKEN); - Log_OC.d(TAG, "Got ACCESS TOKEN: " + mAuthToken); - mAuthCheckOperation = new ExistenceCheckOperation("", this, false); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, true); - client.setBearerCredentials(mAuthToken); - mAuthCheckOperation.execute(client, this, mHandler); - - } else { - updateAuthStatusIconAndText(result); - showAuthStatus(); - Log_OC.d(TAG, "Access failed: " + result.getLogMessage()); - } - } - - - /** - * Processes the result of the access check performed to try the user credentials. - * - * Creates a new account through the AccountManager. - * - * @param operation Access check performed. - * @param result Result of the operation. - */ - private void onAuthorizationCheckFinish(ExistenceCheckOperation operation, RemoteOperationResult result) { - try { - dismissDialog(DIALOG_LOGIN_PROGRESS); - } catch (IllegalArgumentException e) { - // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens - } - - if (result.isSuccess()) { - Log_OC.d(TAG, "Successful access - time to save the account"); - - boolean success = false; - if (mAction == ACTION_CREATE) { - success = createAccount(); - - } else { - success = updateToken(); - } - - if (success) { - finish(); - } - - } else if (result.isServerFail() || result.isException()) { - /// if server fail or exception in authorization, the UI is updated as when a server check failed - mServerIsChecked = true; - mServerIsValid = false; - mIsSslConn = false; - mOcServerChkOperation = null; - mDiscoveredVersion = null; - mHostBaseUrl = normalizeUrl(mHostUrlInput.getText().toString()); - - // update status icon and text - updateServerStatusIconAndText(result); - showServerStatus(); - mAuthStatusIcon = 0; - mAuthStatusText = 0; - showAuthStatus(); - - // update input controls state - showRefreshButton(); - mOkButton.setEnabled(false); - - // very special case (TODO: move to a common place for all the remote operations) (dangerous here?) - if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) { - mLastSslUntrustedServerResult = result; - showDialog(DIALOG_SSL_VALIDATOR); - } - - } else { // authorization fail due to client side - probably wrong credentials - updateAuthStatusIconAndText(result); - showAuthStatus(); - Log_OC.d(TAG, "Access failed: " + result.getLogMessage()); - } - - } - - - /** - * Sets the proper response to get that the Account Authenticator that started this activity saves - * a new authorization token for mAccount. - */ - private boolean updateToken() { - Bundle response = new Bundle(); - response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name); - response.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type); - - if (MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType)) { - response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken); - // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention - mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken); - - } else if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType)) { - String username = getUserNameForSamlSso(); - if (!mUsernameInput.getText().toString().equals(username)) { - // fail - not a new account, but an existing one; disallow - RemoteOperationResult result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_THE_SAME); - updateAuthStatusIconAndText(result); - showAuthStatus(); - Log_OC.d(TAG, result.getLogMessage()); - - return false; - } - - response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken); - // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention - mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken); - - } else { - response.putString(AccountManager.KEY_AUTHTOKEN, mPasswordInput.getText().toString()); - mAccountMgr.setPassword(mAccount, mPasswordInput.getText().toString()); - } - setAccountAuthenticatorResult(response); - - return true; - } - - - /** - * Creates a new account through the Account Authenticator that started this activity. - * - * This makes the account permanent. - * - * TODO Decide how to name the OAuth accounts - */ - private boolean createAccount() { - /// create and save new ownCloud account - boolean isOAuth = MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType); - boolean isSaml = MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType); - - Uri uri = Uri.parse(mHostBaseUrl); - String username = mUsernameInput.getText().toString().trim(); - if (isSaml) { - username = getUserNameForSamlSso(); - - } else if (isOAuth) { - username = "OAuth_user" + (new java.util.Random(System.currentTimeMillis())).nextLong(); - } - String accountName = username + "@" + uri.getHost(); - if (uri.getPort() >= 0) { - accountName += ":" + uri.getPort(); - } - mAccount = new Account(accountName, MainApp.getAccountType()); - if (AccountUtils.exists(mAccount, getApplicationContext())) { - // fail - not a new account, but an existing one; disallow - RemoteOperationResult result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_NEW); - updateAuthStatusIconAndText(result); - showAuthStatus(); - Log_OC.d(TAG, result.getLogMessage()); - return false; - - } else { - - if (isOAuth || isSaml) { - mAccountMgr.addAccountExplicitly(mAccount, "", null); // with external authorizations, the password is never input in the app - } else { - mAccountMgr.addAccountExplicitly(mAccount, mPasswordInput.getText().toString(), null); - } - - /// add the new account as default in preferences, if there is none already - Account defaultAccount = AccountUtils.getCurrentOwnCloudAccount(this); - if (defaultAccount == null) { - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(this).edit(); - editor.putString("select_oc_account", accountName); - editor.commit(); - } - - /// prepare result to return to the Authenticator - // TODO check again what the Authenticator makes with it; probably has the same effect as addAccountExplicitly, but it's not well done - final Intent intent = new Intent(); - intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, MainApp.getAccountType()); - intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mAccount.name); - /*if (!isOAuth) - intent.putExtra(AccountManager.KEY_AUTHTOKEN, MainApp.getAccountType()); */ - intent.putExtra(AccountManager.KEY_USERDATA, username); - if (isOAuth || isSaml) { - mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken); - } - /// add user data to the new account; TODO probably can be done in the last parameter addAccountExplicitly, or in KEY_USERDATA - mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION, mDiscoveredVersion.toString()); - mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL, mHostBaseUrl); - if (isSaml) { - mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_SAML_WEB_SSO, "TRUE"); - } else if (isOAuth) { - mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2, "TRUE"); - } - - setAccountAuthenticatorResult(intent.getExtras()); - setResult(RESULT_OK, intent); - - /// immediately request for the synchronization of the new account - Bundle bundle = new Bundle(); - bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - ContentResolver.requestSync(mAccount, MainApp.getAuthTokenType(), bundle); - syncAccount(); -// Bundle bundle = new Bundle(); -// bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); -// ContentResolver.requestSync(mAccount, MainApp.getAuthTokenType(), bundle); - return true; - } - } - - - private String getUserNameForSamlSso() { - if (mAuthToken != null) { - String [] cookies = mAuthToken.split(";"); - for (int i=0; i 0) { - Log_OC.d(TAG, "Successful SSO - time to save the account"); - onSamlDialogSuccess(sessionCookies); - Fragment fd = getSupportFragmentManager().findFragmentByTag(TAG_SAML_DIALOG); - if (fd != null && fd instanceof SherlockDialogFragment) { - Dialog d = ((SherlockDialogFragment)fd).getDialog(); - if (d != null && d.isShowing()) { - d.dismiss(); - } - } - - } else { - // TODO - show fail - Log_OC.d(TAG, "SSO failed"); - } - - } - - /** Show auth_message - * - * @param message - */ - private void showAuthMessage(String message) { - mAuthMessage.setVisibility(View.VISIBLE); - mAuthMessage.setText(message); - } - - private void hideAuthMessage() { - mAuthMessage.setVisibility(View.GONE); - } - - private void syncAccount(){ - /// immediately request for the synchronization of the new account - Bundle bundle = new Bundle(); - bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - ContentResolver.requestSync(mAccount, MainApp.getAuthTokenType(), bundle); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType) && - mHostUrlInput.hasFocus() && event.getAction() == MotionEvent.ACTION_DOWN) { - checkOcServer(); - } - return super.onTouchEvent(event); - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/authentication/OAuth2Constants.java b/src/de/mobilcom/debitel/cloud/android/authentication/OAuth2Constants.java deleted file mode 100644 index bf812706..00000000 --- a/src/de/mobilcom/debitel/cloud/android/authentication/OAuth2Constants.java +++ /dev/null @@ -1,53 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.authentication; - -/** - * Constant values for OAuth 2 protocol. - * - * Includes required and optional parameter NAMES used in the 'authorization code' grant type. - * - * @author David A. Velasco - */ - -public class OAuth2Constants { - - /// Parameters to send to the Authorization Endpoint - public static final String KEY_RESPONSE_TYPE = "response_type"; - public static final String KEY_REDIRECT_URI = "redirect_uri"; - public static final String KEY_CLIENT_ID = "client_id"; - public static final String KEY_SCOPE = "scope"; - public static final String KEY_STATE = "state"; - - /// Additional parameters to send to the Token Endpoint - public static final String KEY_GRANT_TYPE = "grant_type"; - public static final String KEY_CODE = "code"; - - /// Parameters received in an OK response from the Token Endpoint - public static final String KEY_ACCESS_TOKEN = "access_token"; - public static final String KEY_TOKEN_TYPE = "token_type"; - public static final String KEY_EXPIRES_IN = "expires_in"; - public static final String KEY_REFRESH_TOKEN = "refresh_token"; - - /// Parameters in an ERROR response - public static final String KEY_ERROR = "error"; - public static final String KEY_ERROR_DESCRIPTION = "error_description"; - public static final String KEY_ERROR_URI = "error_uri"; - public static final String VALUE_ERROR_ACCESS_DENIED = "access_denied"; - -} diff --git a/src/de/mobilcom/debitel/cloud/android/authentication/SsoWebViewClient.java b/src/de/mobilcom/debitel/cloud/android/authentication/SsoWebViewClient.java deleted file mode 100644 index 550fa6d0..00000000 --- a/src/de/mobilcom/debitel/cloud/android/authentication/SsoWebViewClient.java +++ /dev/null @@ -1,175 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.authentication; - -import java.lang.ref.WeakReference; - -import de.mobilcom.debitel.cloud.android.Log_OC; - -import android.graphics.Bitmap; -import android.os.Handler; -import android.os.Message; -import android.view.View; -import android.webkit.CookieManager; -import android.webkit.WebView; -import android.webkit.WebViewClient; - - -/** - * Custom {@link WebViewClient} client aimed to catch the end of a single-sign-on process - * running in the {@link WebView} that is attached to. - * - * Assumes that the single-sign-on is kept thanks to a cookie set at the end of the - * authentication process. - * - * @author David A. Velasco - */ -public class SsoWebViewClient extends WebViewClient { - - private static final String TAG = SsoWebViewClient.class.getSimpleName(); - - public interface SsoWebViewClientListener { - public void onSsoFinished(String sessionCookie); - } - - private Handler mListenerHandler; - private WeakReference mListenerRef; - private String mTargetUrl; - private String mLastReloadedUrlAtError; - - public SsoWebViewClient (Handler listenerHandler, SsoWebViewClientListener listener) { - mListenerHandler = listenerHandler; - mListenerRef = new WeakReference(listener); - mTargetUrl = "fake://url.to.be.set"; - mLastReloadedUrlAtError = null; - } - - public String getTargetUrl() { - return mTargetUrl; - } - - public void setTargetUrl(String targetUrl) { - mTargetUrl = targetUrl; - } - - @Override - public void onPageStarted (WebView view, String url, Bitmap favicon) { - Log_OC.d(TAG, "onPageStarted : " + url); - super.onPageStarted(view, url, favicon); - } - - @Override - public void onFormResubmission (WebView view, Message dontResend, Message resend) { - Log_OC.d(TAG, "onFormResubMission "); - - // necessary to grant reload of last page when device orientation is changed after sending a form - resend.sendToTarget(); - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - return false; - } - - @Override - public void onReceivedError (WebView view, int errorCode, String description, String failingUrl) { - Log_OC.e(TAG, "onReceivedError : " + failingUrl + ", code " + errorCode + ", description: " + description); - if (!failingUrl.equals(mLastReloadedUrlAtError)) { - view.reload(); - mLastReloadedUrlAtError = failingUrl; - } else { - mLastReloadedUrlAtError = null; - super.onReceivedError(view, errorCode, description, failingUrl); - } - } - - @Override - public void onPageFinished (WebView view, String url) { - Log_OC.d(TAG, "onPageFinished : " + url); - mLastReloadedUrlAtError = null; - if (url.startsWith(mTargetUrl)) { - view.setVisibility(View.GONE); - CookieManager cookieManager = CookieManager.getInstance(); - final String cookies = cookieManager.getCookie(url); - //Log_OC.d(TAG, "Cookies: " + cookies); - if (mListenerHandler != null && mListenerRef != null) { - // this is good idea because onPageFinished is not running in the UI thread - mListenerHandler.post(new Runnable() { - @Override - public void run() { - SsoWebViewClientListener listener = mListenerRef.get(); - if (listener != null) { - listener.onSsoFinished(cookies); - } - } - }); - } - } - - } - - /* - @Override - public void doUpdateVisitedHistory (WebView view, String url, boolean isReload) { - Log_OC.d(TAG, "doUpdateVisitedHistory : " + url); - } - - @Override - public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) { - Log_OC.d(TAG, "onReceivedSslError : " + error); - } - - @Override - public void onReceivedHttpAuthRequest (WebView view, HttpAuthHandler handler, String host, String realm) { - Log_OC.d(TAG, "onReceivedHttpAuthRequest : " + host); - } - - @Override - public WebResourceResponse shouldInterceptRequest (WebView view, String url) { - Log_OC.d(TAG, "shouldInterceptRequest : " + url); - return null; - } - - @Override - public void onLoadResource (WebView view, String url) { - Log_OC.d(TAG, "onLoadResource : " + url); - } - - @Override - public void onReceivedLoginRequest (WebView view, String realm, String account, String args) { - Log_OC.d(TAG, "onReceivedLoginRequest : " + realm + ", " + account + ", " + args); - } - - @Override - public void onScaleChanged (WebView view, float oldScale, float newScale) { - Log_OC.d(TAG, "onScaleChanged : " + oldScale + " -> " + newScale); - super.onScaleChanged(view, oldScale, newScale); - } - - @Override - public void onUnhandledKeyEvent (WebView view, KeyEvent event) { - Log_OC.d(TAG, "onUnhandledKeyEvent : " + event); - } - - @Override - public boolean shouldOverrideKeyEvent (WebView view, KeyEvent event) { - Log_OC.d(TAG, "shouldOverrideKeyEvent : " + event); - return false; - } - */ -} diff --git a/src/de/mobilcom/debitel/cloud/android/datamodel/DataStorageManager.java b/src/de/mobilcom/debitel/cloud/android/datamodel/DataStorageManager.java deleted file mode 100644 index bbe06fc9..00000000 --- a/src/de/mobilcom/debitel/cloud/android/datamodel/DataStorageManager.java +++ /dev/null @@ -1,52 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.datamodel; - -import java.util.List; -import java.util.Vector; - -public interface DataStorageManager { - - public static final int ROOT_PARENT_ID = 0; - - public OCFile getFileByPath(String path); - - public OCFile getFileById(long id); - - public boolean fileExists(String path); - - public boolean fileExists(long id); - - public boolean saveFile(OCFile file); - - public void saveFiles(List files); - - public Vector getDirectoryContent(OCFile f); - - public void removeFile(OCFile file, boolean removeLocalCopy); - - public void removeDirectory(OCFile dir, boolean removeDBData, boolean removeLocalContent); - - public void moveDirectory(OCFile dir, String newPath); - - public Vector getDirectoryImages(OCFile mParentFolder); - - public void calculateFolderSize(long id); - -} diff --git a/src/de/mobilcom/debitel/cloud/android/datamodel/FileDataStorageManager.java b/src/de/mobilcom/debitel/cloud/android/datamodel/FileDataStorageManager.java deleted file mode 100644 index 32aa09c0..00000000 --- a/src/de/mobilcom/debitel/cloud/android/datamodel/FileDataStorageManager.java +++ /dev/null @@ -1,689 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.datamodel; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Vector; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.db.ProviderMeta.ProviderTableMeta; -import de.mobilcom.debitel.cloud.android.utils.FileStorageUtils; - -import android.accounts.Account; -import android.content.ContentProviderClient; -import android.content.ContentProviderOperation; -import android.content.ContentProviderResult; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.OperationApplicationException; -import android.database.Cursor; -import android.net.Uri; -import android.os.RemoteException; - -public class FileDataStorageManager implements DataStorageManager { - - private ContentResolver mContentResolver; - private ContentProviderClient mContentProvider; - private Account mAccount; - - private static String TAG = "FileDataStorageManager"; - - public FileDataStorageManager(Account account, ContentResolver cr) { - mContentProvider = null; - mContentResolver = cr; - mAccount = account; - } - - public FileDataStorageManager(Account account, ContentProviderClient cp) { - mContentProvider = cp; - mContentResolver = null; - mAccount = account; - } - - @Override - public OCFile getFileByPath(String path) { - Cursor c = getCursorForValue(ProviderTableMeta.FILE_PATH, path); - OCFile file = null; - if (c.moveToFirst()) { - file = createFileInstance(c); - } - c.close(); - if (file == null && OCFile.PATH_SEPARATOR.equals(path)) { - return createRootDir(); // root should always exist - } - return file; - } - - - private OCFile createRootDir() { - OCFile file = new OCFile(OCFile.PATH_SEPARATOR); - file.setMimetype("DIR"); - file.setParentId(DataStorageManager.ROOT_PARENT_ID); - saveFile(file); - return file; - } - - @Override - public OCFile getFileById(long id) { - Cursor c = getCursorForValue(ProviderTableMeta._ID, String.valueOf(id)); - OCFile file = null; - if (c.moveToFirst()) { - file = createFileInstance(c); - } - c.close(); - return file; - } - - public OCFile getFileByLocalPath(String path) { - Cursor c = getCursorForValue(ProviderTableMeta.FILE_STORAGE_PATH, path); - OCFile file = null; - if (c.moveToFirst()) { - file = createFileInstance(c); - } - c.close(); - return file; - } - - @Override - public boolean fileExists(long id) { - return fileExists(ProviderTableMeta._ID, String.valueOf(id)); - } - - @Override - public boolean fileExists(String path) { - return fileExists(ProviderTableMeta.FILE_PATH, path); - } - - @Override - public boolean saveFile(OCFile file) { - boolean overriden = false; - ContentValues cv = new ContentValues(); - cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp()); - cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, file.getModificationTimestampAtLastSyncForData()); - cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp()); - cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength()); - cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype()); - cv.put(ProviderTableMeta.FILE_NAME, file.getFileName()); - if (file.getParentId() != 0) - cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId()); - cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath()); - if (!file.isDirectory()) - cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath()); - cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name); - cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties()); - cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData()); - cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.keepInSync() ? 1 : 0); - - boolean sameRemotePath = fileExists(file.getRemotePath()); - boolean changesSizeOfAncestors = false; - if (sameRemotePath || - fileExists(file.getFileId()) ) { // for renamed files; no more delete and create - - OCFile oldFile = null; - if (sameRemotePath) { - oldFile = getFileByPath(file.getRemotePath()); - file.setFileId(oldFile.getFileId()); - } else { - oldFile = getFileById(file.getFileId()); - } - changesSizeOfAncestors = (oldFile.getFileLength() != file.getFileLength()); - - overriden = true; - if (getContentResolver() != null) { - getContentResolver().update(ProviderTableMeta.CONTENT_URI, cv, - ProviderTableMeta._ID + "=?", - new String[] { String.valueOf(file.getFileId()) }); - } else { - try { - getContentProvider().update(ProviderTableMeta.CONTENT_URI, - cv, ProviderTableMeta._ID + "=?", - new String[] { String.valueOf(file.getFileId()) }); - } catch (RemoteException e) { - Log_OC.e(TAG, - "Fail to insert insert file to database " - + e.getMessage()); - } - } - } else { - changesSizeOfAncestors = true; - Uri result_uri = null; - if (getContentResolver() != null) { - result_uri = getContentResolver().insert( - ProviderTableMeta.CONTENT_URI_FILE, cv); - } else { - try { - result_uri = getContentProvider().insert( - ProviderTableMeta.CONTENT_URI_FILE, cv); - } catch (RemoteException e) { - Log_OC.e(TAG, - "Fail to insert insert file to database " - + e.getMessage()); - } - } - if (result_uri != null) { - long new_id = Long.parseLong(result_uri.getPathSegments() - .get(1)); - file.setFileId(new_id); - } - } - - if (file.isDirectory()) { - calculateFolderSize(file.getFileId()); - if (file.needsUpdatingWhileSaving()) { - for (OCFile f : getDirectoryContent(file)) - saveFile(f); - } - } - - if (changesSizeOfAncestors || file.isDirectory()) { - updateSizesToTheRoot(file.getParentId()); - } - - return overriden; - } - - - @Override - public void saveFiles(List files) { - - Iterator filesIt = files.iterator(); - ArrayList operations = new ArrayList(files.size()); - OCFile file = null; - - // prepare operations to perform - while (filesIt.hasNext()) { - file = filesIt.next(); - ContentValues cv = new ContentValues(); - cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp()); - cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, file.getModificationTimestampAtLastSyncForData()); - cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp()); - cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength()); - cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype()); - cv.put(ProviderTableMeta.FILE_NAME, file.getFileName()); - if (file.getParentId() != 0) - cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId()); - cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath()); - if (!file.isDirectory()) - cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath()); - cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name); - cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties()); - cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData()); - cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.keepInSync() ? 1 : 0); - - if (fileExists(file.getRemotePath())) { - OCFile oldFile = getFileByPath(file.getRemotePath()); - file.setFileId(oldFile.getFileId()); - - if (file.isDirectory()) { - cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, oldFile.getFileLength()); - file.setFileLength(oldFile.getFileLength()); - } - - operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). - withValues(cv). - withSelection( ProviderTableMeta._ID + "=?", - new String[] { String.valueOf(file.getFileId()) }) - .build()); - - } else if (fileExists(file.getFileId())) { - OCFile oldFile = getFileById(file.getFileId()); - if (file.getStoragePath() == null && oldFile.getStoragePath() != null) - file.setStoragePath(oldFile.getStoragePath()); - - if (!file.isDirectory()) - cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath()); - else { - cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, oldFile.getFileLength()); - file.setFileLength(oldFile.getFileLength()); - } - - operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). - withValues(cv). - withSelection( ProviderTableMeta._ID + "=?", - new String[] { String.valueOf(file.getFileId()) }) - .build()); - - } else { - operations.add(ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI).withValues(cv).build()); - } - } - - // apply operations in batch - ContentProviderResult[] results = null; - try { - if (getContentResolver() != null) { - results = getContentResolver().applyBatch(MainApp.getAuthority(), operations); - - } else { - results = getContentProvider().applyBatch(operations); - } - - } catch (OperationApplicationException e) { - Log_OC.e(TAG, "Fail to update/insert list of files to database " + e.getMessage()); - - } catch (RemoteException e) { - Log_OC.e(TAG, "Fail to update/insert list of files to database " + e.getMessage()); - } - - // update new id in file objects for insertions - if (results != null) { - long newId; - for (int i=0; i getDirectoryContent(OCFile f) { - if (f != null && f.isDirectory() && f.getFileId() != -1) { - return getDirectoryContent(f.getFileId()); - - } else { - return new Vector(); - } - } - - private Vector getDirectoryContent(long parentId) { - - Vector ret = new Vector(); - - Uri req_uri = Uri.withAppendedPath( - ProviderTableMeta.CONTENT_URI_DIR, - String.valueOf(parentId)); - Cursor c = null; - - if (getContentProvider() != null) { - try { - c = getContentProvider().query(req_uri, null, - ProviderTableMeta.FILE_PARENT + "=?" , - new String[] { String.valueOf(parentId)}, null); - } catch (RemoteException e) { - Log_OC.e(TAG, e.getMessage()); - return ret; - } - } else { - c = getContentResolver().query(req_uri, null, - ProviderTableMeta.FILE_PARENT + "=?" , - new String[] { String.valueOf(parentId)}, null); - } - - if (c.moveToFirst()) { - do { - OCFile child = createFileInstance(c); - ret.add(child); - } while (c.moveToNext()); - } - - c.close(); - - Collections.sort(ret); - - return ret; - } - - - - private boolean fileExists(String cmp_key, String value) { - Cursor c; - if (getContentResolver() != null) { - c = getContentResolver() - .query(ProviderTableMeta.CONTENT_URI, - null, - cmp_key + "=? AND " - + ProviderTableMeta.FILE_ACCOUNT_OWNER - + "=?", - new String[] { value, mAccount.name }, null); - } else { - try { - c = getContentProvider().query( - ProviderTableMeta.CONTENT_URI, - null, - cmp_key + "=? AND " - + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?", - new String[] { value, mAccount.name }, null); - } catch (RemoteException e) { - Log_OC.e(TAG, - "Couldn't determine file existance, assuming non existance: " - + e.getMessage()); - return false; - } - } - boolean retval = c.moveToFirst(); - c.close(); - return retval; - } - - private Cursor getCursorForValue(String key, String value) { - Cursor c = null; - if (getContentResolver() != null) { - c = getContentResolver() - .query(ProviderTableMeta.CONTENT_URI, - null, - key + "=? AND " - + ProviderTableMeta.FILE_ACCOUNT_OWNER - + "=?", - new String[] { value, mAccount.name }, null); - } else { - try { - c = getContentProvider().query( - ProviderTableMeta.CONTENT_URI, - null, - key + "=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER - + "=?", new String[] { value, mAccount.name }, - null); - } catch (RemoteException e) { - Log_OC.e(TAG, "Could not get file details: " + e.getMessage()); - c = null; - } - } - return c; - } - - private OCFile createFileInstance(Cursor c) { - OCFile file = null; - if (c != null) { - file = new OCFile(c.getString(c - .getColumnIndex(ProviderTableMeta.FILE_PATH))); - file.setFileId(c.getLong(c.getColumnIndex(ProviderTableMeta._ID))); - file.setParentId(c.getLong(c - .getColumnIndex(ProviderTableMeta.FILE_PARENT))); - file.setMimetype(c.getString(c - .getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE))); - if (!file.isDirectory()) { - file.setStoragePath(c.getString(c - .getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH))); - if (file.getStoragePath() == null) { - // try to find existing file and bind it with current account; - with the current update of SynchronizeFolderOperation, this won't be necessary anymore after a full synchronization of the account - File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); - if (f.exists()) { - file.setStoragePath(f.getAbsolutePath()); - file.setLastSyncDateForData(f.lastModified()); - } - } - } - file.setFileLength(c.getLong(c - .getColumnIndex(ProviderTableMeta.FILE_CONTENT_LENGTH))); - file.setCreationTimestamp(c.getLong(c - .getColumnIndex(ProviderTableMeta.FILE_CREATION))); - file.setModificationTimestamp(c.getLong(c - .getColumnIndex(ProviderTableMeta.FILE_MODIFIED))); - file.setModificationTimestampAtLastSyncForData(c.getLong(c - .getColumnIndex(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA))); - file.setLastSyncDateForProperties(c.getLong(c - .getColumnIndex(ProviderTableMeta.FILE_LAST_SYNC_DATE))); - file.setLastSyncDateForData(c.getLong(c. - getColumnIndex(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA))); - file.setKeepInSync(c.getInt( - c.getColumnIndex(ProviderTableMeta.FILE_KEEP_IN_SYNC)) == 1 ? true : false); - } - return file; - } - - @Override - public void removeFile(OCFile file, boolean removeLocalCopy) { - Uri file_uri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, ""+file.getFileId()); - if (getContentProvider() != null) { - try { - getContentProvider().delete(file_uri, - ProviderTableMeta.FILE_ACCOUNT_OWNER+"=?", - new String[]{mAccount.name}); - } catch (RemoteException e) { - e.printStackTrace(); - } - } else { - getContentResolver().delete(file_uri, - ProviderTableMeta.FILE_ACCOUNT_OWNER+"=?", - new String[]{mAccount.name}); - } - if (file.isDown() && removeLocalCopy) { - new File(file.getStoragePath()).delete(); - } - if (file.isDirectory() && removeLocalCopy) { - File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); - if (f.exists() && f.isDirectory() && (f.list() == null || f.list().length == 0)) { - f.delete(); - } - } - - if (file.getFileLength() > 0) { - updateSizesToTheRoot(file.getParentId()); - } - } - - @Override - public void removeDirectory(OCFile dir, boolean removeDBData, boolean removeLocalContent) { - // TODO consider possible failures - if (dir != null && dir.isDirectory() && dir.getFileId() != -1) { - Vector children = getDirectoryContent(dir); - if (children.size() > 0) { - OCFile child = null; - for (int i=0; i 0) { - updateSizesToTheRoot(dir.getParentId()); - } - } - } - - - /** - * Updates database for a folder that was moved to a different location. - * - * TODO explore better (faster) implementations - * TODO throw exceptions up ! - */ - @Override - public void moveDirectory(OCFile dir, String newPath) { - // TODO check newPath - - if (dir != null && dir.isDirectory() && dir.fileExists() && !dir.getFileName().equals(OCFile.PATH_SEPARATOR)) { - /// 1. get all the descendants of 'dir' in a single QUERY (including 'dir') - Cursor c = null; - if (getContentProvider() != null) { - try { - c = getContentProvider().query(ProviderTableMeta.CONTENT_URI, - null, - ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ? ", - new String[] { mAccount.name, dir.getRemotePath() + "%" }, ProviderTableMeta.FILE_PATH + " ASC "); - } catch (RemoteException e) { - Log_OC.e(TAG, e.getMessage()); - } - } else { - c = getContentResolver().query(ProviderTableMeta.CONTENT_URI, - null, - ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ? ", - new String[] { mAccount.name, dir.getRemotePath() + "%" }, ProviderTableMeta.FILE_PATH + " ASC "); - } - - /// 2. prepare a batch of update operations to change all the descendants - ArrayList operations = new ArrayList(c.getCount()); - int lengthOfOldPath = dir.getRemotePath().length(); - String defaultSavePath = FileStorageUtils.getSavePath(mAccount.name); - int lengthOfOldStoragePath = defaultSavePath.length() + lengthOfOldPath; - if (c.moveToFirst()) { - do { - ContentValues cv = new ContentValues(); // don't take the constructor out of the loop and clear the object - OCFile child = createFileInstance(c); - cv.put(ProviderTableMeta.FILE_PATH, newPath + child.getRemotePath().substring(lengthOfOldPath)); - if (child.getStoragePath() != null && child.getStoragePath().startsWith(defaultSavePath)) { - cv.put(ProviderTableMeta.FILE_STORAGE_PATH, defaultSavePath + newPath + child.getStoragePath().substring(lengthOfOldStoragePath)); - } - operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). - withValues(cv). - withSelection( ProviderTableMeta._ID + "=?", - new String[] { String.valueOf(child.getFileId()) }) - .build()); - } while (c.moveToNext()); - } - c.close(); - - /// 3. apply updates in batch - try { - if (getContentResolver() != null) { - getContentResolver().applyBatch(MainApp.getAuthority(), operations); - - } else { - getContentProvider().applyBatch(operations); - } - - } catch (OperationApplicationException e) { - Log_OC.e(TAG, "Fail to update descendants of " + dir.getFileId() + " in database", e); - - } catch (RemoteException e) { - Log_OC.e(TAG, "Fail to update desendants of " + dir.getFileId() + " in database", e); - } - - } - } - - @Override - public Vector getDirectoryImages(OCFile directory) { - Vector ret = new Vector(); - if (directory != null) { - // TODO better implementation, filtering in the access to database (if possible) instead of here - Vector tmp = getDirectoryContent(directory); - OCFile current = null; - for (int i=0; i files = getDirectoryContent(id); - - for (OCFile f: files) - { - folderSize = folderSize + f.getFileLength(); - } - - updateSize(id, folderSize); - } - - /** - * Update the size value of an OCFile in DB - */ - private int updateSize(long id, long size) { - ContentValues cv = new ContentValues(); - cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, size); - int result = -1; - if (getContentResolver() != null) { - result = getContentResolver().update(ProviderTableMeta.CONTENT_URI, cv, ProviderTableMeta._ID + "=?", - new String[] { String.valueOf(id) }); - } else { - try { - result = getContentProvider().update(ProviderTableMeta.CONTENT_URI, cv, ProviderTableMeta._ID + "=?", - new String[] { String.valueOf(id) }); - } catch (RemoteException e) { - Log_OC.e(TAG,"Fail to update size column into database " + e.getMessage()); - } - } - return result; - } - - /** - * Update the size of a subtree of folder from a file to the root - * @param parentId: parent of the file - */ - private void updateSizesToTheRoot(long parentId) { - - OCFile file; - - while (parentId != 0) { - - // Update the size of the parent - calculateFolderSize(parentId); - - // search the next parent - file = getFileById(parentId); - parentId = file.getParentId(); - - } - - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/datamodel/OCFile.java b/src/de/mobilcom/debitel/cloud/android/datamodel/OCFile.java deleted file mode 100644 index 1865768f..00000000 --- a/src/de/mobilcom/debitel/cloud/android/datamodel/OCFile.java +++ /dev/null @@ -1,485 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.datamodel; - -import java.io.File; - -import de.mobilcom.debitel.cloud.android.Log_OC; - -import android.os.Parcel; -import android.os.Parcelable; -import android.webkit.MimeTypeMap; - -public class OCFile implements Parcelable, Comparable { - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - @Override - public OCFile createFromParcel(Parcel source) { - return new OCFile(source); - } - - @Override - public OCFile[] newArray(int size) { - return new OCFile[size]; - } - }; - - public static final String PATH_SEPARATOR = "/"; - - private static final String TAG = OCFile.class.getSimpleName(); - - private long mId; - private long mParentId; - private long mLength; - private long mCreationTimestamp; - private long mModifiedTimestamp; - private long mModifiedTimestampAtLastSyncForData; - private String mRemotePath; - private String mLocalPath; - private String mMimeType; - private boolean mNeedsUpdating; - private long mLastSyncDateForProperties; - private long mLastSyncDateForData; - private boolean mKeepInSync; - - private String mEtag; - - /** - * Create new {@link OCFile} with given path. - * - * The path received must be URL-decoded. Path separator must be OCFile.PATH_SEPARATOR, and it must be the first character in 'path'. - * - * @param path The remote path of the file. - */ - public OCFile(String path) { - resetData(); - mNeedsUpdating = false; - if (path == null || path.length() <= 0 || !path.startsWith(PATH_SEPARATOR)) { - throw new IllegalArgumentException("Trying to create a OCFile with a non valid remote path: " + path); - } - mRemotePath = path; - } - - /** - * Reconstruct from parcel - * - * @param source The source parcel - */ - private OCFile(Parcel source) { - mId = source.readLong(); - mParentId = source.readLong(); - mLength = source.readLong(); - mCreationTimestamp = source.readLong(); - mModifiedTimestamp = source.readLong(); - mModifiedTimestampAtLastSyncForData = source.readLong(); - mRemotePath = source.readString(); - mLocalPath = source.readString(); - mMimeType = source.readString(); - mNeedsUpdating = source.readInt() == 0; - mKeepInSync = source.readInt() == 1; - mLastSyncDateForProperties = source.readLong(); - mLastSyncDateForData = source.readLong(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeLong(mId); - dest.writeLong(mParentId); - dest.writeLong(mLength); - dest.writeLong(mCreationTimestamp); - dest.writeLong(mModifiedTimestamp); - dest.writeLong(mModifiedTimestampAtLastSyncForData); - dest.writeString(mRemotePath); - dest.writeString(mLocalPath); - dest.writeString(mMimeType); - dest.writeInt(mNeedsUpdating ? 1 : 0); - dest.writeInt(mKeepInSync ? 1 : 0); - dest.writeLong(mLastSyncDateForProperties); - dest.writeLong(mLastSyncDateForData); - } - - /** - * Gets the ID of the file - * - * @return the file ID - */ - public long getFileId() { - return mId; - } - - /** - * Returns the remote path of the file on ownCloud - * - * @return The remote path to the file - */ - public String getRemotePath() { - return mRemotePath; - } - - /** - * Can be used to check, whether or not this file exists in the database - * already - * - * @return true, if the file exists in the database - */ - public boolean fileExists() { - return mId != -1; - } - - /** - * Use this to find out if this file is a Directory - * - * @return true if it is a directory - */ - public boolean isDirectory() { - return mMimeType != null && mMimeType.equals("DIR"); - } - - /** - * Use this to check if this file is available locally - * - * @return true if it is - */ - public boolean isDown() { - if (mLocalPath != null && mLocalPath.length() > 0) { - File file = new File(mLocalPath); - return (file.exists()); - } - return false; - } - - /** - * The path, where the file is stored locally - * - * @return The local path to the file - */ - public String getStoragePath() { - return mLocalPath; - } - - /** - * Can be used to set the path where the file is stored - * - * @param storage_path to set - */ - public void setStoragePath(String storage_path) { - mLocalPath = storage_path; - } - - /** - * Get a UNIX timestamp of the file creation time - * - * @return A UNIX timestamp of the time that file was created - */ - public long getCreationTimestamp() { - return mCreationTimestamp; - } - - /** - * Set a UNIX timestamp of the time the file was created - * - * @param creation_timestamp to set - */ - public void setCreationTimestamp(long creation_timestamp) { - mCreationTimestamp = creation_timestamp; - } - - /** - * Get a UNIX timestamp of the file modification time. - * - * @return A UNIX timestamp of the modification time, corresponding to the value returned by the server - * in the last synchronization of the properties of this file. - */ - public long getModificationTimestamp() { - return mModifiedTimestamp; - } - - /** - * Set a UNIX timestamp of the time the time the file was modified. - * - * To update with the value returned by the server in every synchronization of the properties - * of this file. - * - * @param modification_timestamp to set - */ - public void setModificationTimestamp(long modification_timestamp) { - mModifiedTimestamp = modification_timestamp; - } - - - /** - * Get a UNIX timestamp of the file modification time. - * - * @return A UNIX timestamp of the modification time, corresponding to the value returned by the server - * in the last synchronization of THE CONTENTS of this file. - */ - public long getModificationTimestampAtLastSyncForData() { - return mModifiedTimestampAtLastSyncForData; - } - - /** - * Set a UNIX timestamp of the time the time the file was modified. - * - * To update with the value returned by the server in every synchronization of THE CONTENTS - * of this file. - * - * @param modification_timestamp to set - */ - public void setModificationTimestampAtLastSyncForData(long modificationTimestamp) { - mModifiedTimestampAtLastSyncForData = modificationTimestamp; - } - - - - /** - * Returns the filename and "/" for the root directory - * - * @return The name of the file - */ - public String getFileName() { - File f = new File(getRemotePath()); - return f.getName().length() == 0 ? PATH_SEPARATOR : f.getName(); - } - - /** - * Sets the name of the file - * - * Does nothing if the new name is null, empty or includes "/" ; or if the file is the root directory - */ - public void setFileName(String name) { - Log_OC.d(TAG, "OCFile name changin from " + mRemotePath); - if (name != null && name.length() > 0 && !name.contains(PATH_SEPARATOR) && !mRemotePath.equals(PATH_SEPARATOR)) { - String parent = (new File(getRemotePath())).getParent(); - parent = (parent.endsWith(PATH_SEPARATOR)) ? parent : parent + PATH_SEPARATOR; - mRemotePath = parent + name; - if (isDirectory()) { - mRemotePath += PATH_SEPARATOR; - } - Log_OC.d(TAG, "OCFile name changed to " + mRemotePath); - } - } - - /** - * Can be used to get the Mimetype - * - * @return the Mimetype as a String - */ - public String getMimetype() { - return mMimeType; - } - - /** - * Adds a file to this directory. If this file is not a directory, an - * exception gets thrown. - * - * @param file to add - * @throws IllegalStateException if you try to add a something and this is - * not a directory - */ - public void addFile(OCFile file) throws IllegalStateException { - if (isDirectory()) { - file.mParentId = mId; - mNeedsUpdating = true; - return; - } - throw new IllegalStateException( - "This is not a directory where you can add stuff to!"); - } - - /** - * Used internally. Reset all file properties - */ - private void resetData() { - mId = -1; - mRemotePath = null; - mParentId = 0; - mLocalPath = null; - mMimeType = null; - mLength = 0; - mCreationTimestamp = 0; - mModifiedTimestamp = 0; - mModifiedTimestampAtLastSyncForData = 0; - mLastSyncDateForProperties = 0; - mLastSyncDateForData = 0; - mKeepInSync = false; - mNeedsUpdating = false; - } - - /** - * Sets the ID of the file - * - * @param file_id to set - */ - public void setFileId(long file_id) { - mId = file_id; - } - - /** - * Sets the Mime-Type of the - * - * @param mimetype to set - */ - public void setMimetype(String mimetype) { - mMimeType = mimetype; - } - - /** - * Sets the ID of the parent folder - * - * @param parent_id to set - */ - public void setParentId(long parent_id) { - mParentId = parent_id; - } - - /** - * Sets the file size in bytes - * - * @param file_len to set - */ - public void setFileLength(long file_len) { - mLength = file_len; - } - - /** - * Returns the size of the file in bytes - * - * @return The filesize in bytes - */ - public long getFileLength() { - return mLength; - } - - /** - * Returns the ID of the parent Folder - * - * @return The ID - */ - public long getParentId() { - return mParentId; - } - - /** - * Check, if this file needs updating - * - * @return - */ - public boolean needsUpdatingWhileSaving() { - return mNeedsUpdating; - } - - public long getLastSyncDateForProperties() { - return mLastSyncDateForProperties; - } - - public void setLastSyncDateForProperties(long lastSyncDate) { - mLastSyncDateForProperties = lastSyncDate; - } - - public long getLastSyncDateForData() { - return mLastSyncDateForData; - } - - public void setLastSyncDateForData(long lastSyncDate) { - mLastSyncDateForData = lastSyncDate; - } - - public void setKeepInSync(boolean keepInSync) { - mKeepInSync = keepInSync; - } - - public boolean keepInSync() { - return mKeepInSync; - } - - @Override - public int describeContents() { - return this.hashCode(); - } - - @Override - public int compareTo(OCFile another) { - if (isDirectory() && another.isDirectory()) { - return getRemotePath().toLowerCase().compareTo(another.getRemotePath().toLowerCase()); - } else if (isDirectory()) { - return -1; - } else if (another.isDirectory()) { - return 1; - } - return getRemotePath().toLowerCase().compareTo(another.getRemotePath().toLowerCase()); - } - - @Override - public boolean equals(Object o) { - if(o instanceof OCFile){ - OCFile that = (OCFile) o; - if(that != null){ - return this.mId == that.mId; - } - } - - return false; - } - - @Override - public String toString() { - String asString = "[id=%s, name=%s, mime=%s, downloaded=%s, local=%s, remote=%s, parentId=%s, keepInSinc=%s]"; - asString = String.format(asString, Long.valueOf(mId), getFileName(), mMimeType, isDown(), mLocalPath, mRemotePath, Long.valueOf(mParentId), Boolean.valueOf(mKeepInSync)); - return asString; - } - - public String getEtag() { - return mEtag; - } - - public long getLocalModificationTimestamp() { - if (mLocalPath != null && mLocalPath.length() > 0) { - File f = new File(mLocalPath); - return f.lastModified(); - } - return 0; - } - - /** @return 'True' if the file contains audio */ - public boolean isAudio() { - return (mMimeType != null && mMimeType.startsWith("audio/")); - } - - /** @return 'True' if the file contains video */ - public boolean isVideo() { - return (mMimeType != null && mMimeType.startsWith("video/")); - } - - /** @return 'True' if the file contains an image */ - public boolean isImage() { - return ((mMimeType != null && mMimeType.startsWith("image/")) || - getMimeTypeFromName().startsWith("image/")); - } - - public String getMimeTypeFromName() { - String extension = ""; - int pos = mRemotePath.lastIndexOf('.'); - if (pos >= 0) { - extension = mRemotePath.substring(pos + 1); - } - String result = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase()); - return (result != null) ? result : ""; - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/db/DbHandler.java b/src/de/mobilcom/debitel/cloud/android/db/DbHandler.java deleted file mode 100644 index c04c8014..00000000 --- a/src/de/mobilcom/debitel/cloud/android/db/DbHandler.java +++ /dev/null @@ -1,120 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.db; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.MainApp; - -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 - * - * @author Bartek Przybylski - * - */ -public class DbHandler { - private SQLiteDatabase mDB; - private OpenerHelper mHelper; - private final String mDatabaseName; // = "ownCloud"; - 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) { - mHelper = new OpenerHelper(context); - mDB = mHelper.getWritableDatabase(); - mDatabaseName = MainApp.getDBName(); - } - - 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;"); - - } - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/db/ProviderMeta.java b/src/de/mobilcom/debitel/cloud/android/db/ProviderMeta.java deleted file mode 100644 index 28032c03..00000000 --- a/src/de/mobilcom/debitel/cloud/android/db/ProviderMeta.java +++ /dev/null @@ -1,73 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.db; - -import de.mobilcom.debitel.cloud.android.MainApp; - -import android.net.Uri; -import android.provider.BaseColumns; - -/** - * Meta-Class that holds various static field information - * - * @author Bartek Przybylski - * - */ -public class ProviderMeta { - - /* These constants are now in MainApp - public static final String AUTHORITY_FILES = "org.owncloud"; - public static final String DB_FILE = "owncloud.db"; - */ - public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 4; - - private ProviderMeta() { - } - - static public class ProviderTableMeta implements BaseColumns { - public static final String DB_NAME = "filelist"; - 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 String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.owncloud.file"; - public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.owncloud.file"; - - 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 DEFAULT_SORT_ORDER = FILE_NAME - + " collate nocase asc"; - - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/extensions/ExtensionsAvailableActivity.java b/src/de/mobilcom/debitel/cloud/android/extensions/ExtensionsAvailableActivity.java deleted file mode 100644 index e75d39ea..00000000 --- a/src/de/mobilcom/debitel/cloud/android/extensions/ExtensionsAvailableActivity.java +++ /dev/null @@ -1,35 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.extensions; - -import android.os.Bundle; -import android.support.v4.app.FragmentManager; - -import com.actionbarsherlock.app.SherlockFragmentActivity; - -public class ExtensionsAvailableActivity extends SherlockFragmentActivity { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - FragmentManager fm = getSupportFragmentManager(); - ExtensionsAvailableDialog ead = new ExtensionsAvailableDialog(); - ead.show(fm, "extensions_available_dialog"); - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/extensions/ExtensionsAvailableDialog.java b/src/de/mobilcom/debitel/cloud/android/extensions/ExtensionsAvailableDialog.java deleted file mode 100644 index e7e899f0..00000000 --- a/src/de/mobilcom/debitel/cloud/android/extensions/ExtensionsAvailableDialog.java +++ /dev/null @@ -1,69 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.extensions; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.ui.CustomButton; -import android.content.Intent; -import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.View.OnClickListener; - -public class ExtensionsAvailableDialog extends DialogFragment implements - OnClickListener { - - public ExtensionsAvailableDialog() { - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.extensions_available_dialog, - container); - CustomButton btnYes = (CustomButton) view.findViewById(R.id.buttonYes); - CustomButton btnNo = (CustomButton) view.findViewById(R.id.buttonNo); - - btnYes.setOnClickListener(this); - btnNo.setOnClickListener(this); - getDialog().setTitle(R.string.extensions_avail_title); - return view; - } - - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.buttonYes: { - Intent i = new Intent(getActivity(), ExtensionsListActivity.class); - startActivity(i); - getActivity().finish(); - } - break; - case R.id.buttonNo: - getActivity().finish(); - break; - default: - Log_OC.e("EAD", "Button with unknown id clicked " + v.getId()); - } - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/extensions/ExtensionsListActivity.java b/src/de/mobilcom/debitel/cloud/android/extensions/ExtensionsListActivity.java deleted file mode 100644 index 040a86cb..00000000 --- a/src/de/mobilcom/debitel/cloud/android/extensions/ExtensionsListActivity.java +++ /dev/null @@ -1,155 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.extensions; - -import java.util.HashMap; -import java.util.LinkedList; - -import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.methods.GetMethod; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.utils.OwnCloudVersion; - - -import android.R; -import android.app.ListActivity; -import android.os.Bundle; -import android.os.Handler; -import android.widget.SimpleAdapter; - -public class ExtensionsListActivity extends ListActivity { - - private static final String packages_url = "http://alefzero.eu/a/packages.php"; - - private Thread mGetterThread; - private final Handler mHandler = new Handler(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mGetterThread = new Thread(new JsonGetter()); - mGetterThread.start(); - } - - public void done(JSONArray a) { - LinkedList> ll = new LinkedList>(); - for (int i = 0; i < a.length(); ++i) { - try { - ExtensionApplicationEntry ela = new ExtensionApplicationEntry( - ((JSONObject) a.get(i))); - HashMap ss = new HashMap(); - ss.put("NAME", ela.getName()); - ss.put("DESC", ela.getDescription()); - ll.add(ss); - } catch (JSONException e) { - e.printStackTrace(); - } - } - setListAdapter(new SimpleAdapter(this, ll, R.layout.simple_list_item_2, - new String[] { "NAME", "DESC" }, new int[] { - android.R.id.text1, android.R.id.text2 })); - - } - - private class JsonGetter implements Runnable { - - @Override - public void run() { - HttpClient hc = new HttpClient(); - GetMethod gm = new GetMethod(packages_url); - final JSONArray ar; - try { - hc.executeMethod(gm); - Log_OC.e("ASD", gm.getResponseBodyAsString() + ""); - ar = new JSONObject(gm.getResponseBodyAsString()) - .getJSONArray("apps"); - } catch (Exception e) { - e.printStackTrace(); - return; - } - - mHandler.post(new Runnable() { - @Override - public void run() { - done(ar); - } - }); - - } - - } - - private class ExtensionApplicationEntry { - private static final String APP_NAME = "name"; - private static final String APP_VERSION = "version"; - private static final String APP_DESC = "description"; - private static final String APP_ICON = "icon"; - private static final String APP_URL = "download"; - private static final String APP_PLAYID = "play_id"; - - private String mName, mDescription, mIcon, mDownload, mPlayId; - private OwnCloudVersion mVersion; - - public ExtensionApplicationEntry(JSONObject appentry) { - try { - mName = appentry.getString(APP_NAME); - mDescription = appentry.getString(APP_DESC); - mIcon = appentry.getString(APP_ICON); - mDownload = appentry.getString(APP_URL); - mPlayId = appentry.getString(APP_PLAYID); - mVersion = new OwnCloudVersion(appentry.getString(APP_VERSION)); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - public String getName() { - return mName; - } - - public String getDescription() { - return mDescription; - } - - @SuppressWarnings("unused") - public String getIcon() { - return mIcon; - } - - @SuppressWarnings("unused") - public String getDownload() { - return mDownload; - } - - @SuppressWarnings("unused") - public String getPlayId() { - return mPlayId; - } - - @SuppressWarnings("unused") - public OwnCloudVersion getVersion() { - return mVersion; - } - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/files/BootupBroadcastReceiver.java b/src/de/mobilcom/debitel/cloud/android/files/BootupBroadcastReceiver.java deleted file mode 100644 index 9dc26d83..00000000 --- a/src/de/mobilcom/debitel/cloud/android/files/BootupBroadcastReceiver.java +++ /dev/null @@ -1,46 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.files; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.files.services.FileObserverService; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -public class BootupBroadcastReceiver extends BroadcastReceiver { - - private static String TAG = "BootupBroadcastReceiver"; - - @Override - public void onReceive(Context context, Intent intent) { - if (!intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { - Log_OC.wtf(TAG, "Incorrect action sent " + intent.getAction()); - return; - } - Log_OC.d(TAG, "Starting file observer service..."); - Intent i = new Intent(context, FileObserverService.class); - i.putExtra(FileObserverService.KEY_FILE_CMD, - FileObserverService.CMD_INIT_OBSERVED_LIST); - context.startService(i); - Log_OC.d(TAG, "DONE"); - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/files/FileHandler.java b/src/de/mobilcom/debitel/cloud/android/files/FileHandler.java deleted file mode 100644 index 917ffeab..00000000 --- a/src/de/mobilcom/debitel/cloud/android/files/FileHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.files; - -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; - -public interface FileHandler { - - /** - * TODO - */ - public void openFile(OCFile file); - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/files/InstantUploadBroadcastReceiver.java b/src/de/mobilcom/debitel/cloud/android/files/InstantUploadBroadcastReceiver.java deleted file mode 100644 index 3db31aa6..00000000 --- a/src/de/mobilcom/debitel/cloud/android/files/InstantUploadBroadcastReceiver.java +++ /dev/null @@ -1,214 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.files; - -import java.io.File; - - -import android.accounts.Account; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -//import android.content.IntentFilter; -import android.database.Cursor; -import android.net.ConnectivityManager; -import android.net.NetworkInfo.State; -import android.preference.PreferenceManager; -import android.provider.MediaStore.Images.Media; -import android.webkit.MimeTypeMap; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; -import de.mobilcom.debitel.cloud.android.db.DbHandler; -import de.mobilcom.debitel.cloud.android.files.services.FileUploader; -import de.mobilcom.debitel.cloud.android.utils.FileStorageUtils; - -public class InstantUploadBroadcastReceiver extends BroadcastReceiver { - - private static String TAG = "InstantUploadBroadcastReceiver"; - private static final String[] CONTENT_PROJECTION = { Media.DATA, Media.DISPLAY_NAME, Media.MIME_TYPE, Media.SIZE }; - //Unofficial action, works for most devices but not HTC. See: https://github.com/owncloud/android/issues/6 - private static String NEW_PHOTO_ACTION_UNOFFICIAL = "com.android.camera.NEW_PICTURE"; - //Officially supported action since SDK 14: http://developer.android.com/reference/android/hardware/Camera.html#ACTION_NEW_PICTURE - private static String NEW_PHOTO_ACTION = "android.hardware.action.NEW_PICTURE"; - - @Override - public void onReceive(Context context, Intent intent) { - Log_OC.d(TAG, "Received: " + intent.getAction()); - - FileUploader fileUploader = new FileUploader(); - - if (intent.getAction().equals(android.net.ConnectivityManager.CONNECTIVITY_ACTION)) { - handleConnectivityAction(context, intent); - }else if (intent.getAction().equals(NEW_PHOTO_ACTION_UNOFFICIAL)) { - handleNewPhotoAction(context, intent); - Log_OC.d(TAG, "UNOFFICIAL processed: com.android.camera.NEW_PICTURE"); - } else if (intent.getAction().equals(NEW_PHOTO_ACTION)) { - handleNewPhotoAction(context, intent); - Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_PICTURE"); - } else if (intent.getAction().equals(fileUploader.getUploadFinishMessage())) { - handleUploadFinished(context, intent); - } else { - Log_OC.e(TAG, "Incorrect intent sent: " + intent.getAction()); - } - } - - private void handleUploadFinished(Context context, Intent intent) { - // remove successfull uploading, ignore rest for reupload on reconnect - /* - if (intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false)) { - DbHandler db = new DbHandler(context); - String localPath = intent.getStringExtra(FileUploader.EXTRA_OLD_FILE_PATH); - if (!db.removeIUPendingFile(localPath)) { - Log_OC.w(TAG, "Tried to remove non existing instant upload file " + localPath); - } - db.close(); - } - */ - } - - private void handleNewPhotoAction(Context context, Intent intent) { - if (!instantUploadEnabled(context)) { - Log_OC.d(TAG, "Instant upload disabled, aborting uploading"); - return; - } - - Account account = AccountUtils.getCurrentOwnCloudAccount(context); - if (account == null) { - Log_OC.w(TAG, "No owncloud account found for instant upload, aborting"); - return; - } - - Cursor c = context.getContentResolver().query(intent.getData(), CONTENT_PROJECTION, null, null, null); - - if (!c.moveToFirst()) { - Log_OC.e(TAG, "Couldn't resolve given uri: " + intent.getDataString()); - return; - } - - String file_path = c.getString(c.getColumnIndex(Media.DATA)); - String file_name = c.getString(c.getColumnIndex(Media.DISPLAY_NAME)); - String mime_type = c.getString(c.getColumnIndex(Media.MIME_TYPE)); - - c.close(); - Log_OC.e(TAG, file_path + ""); - - // same always temporally the picture to upload - DbHandler db = new DbHandler(context); - db.putFileForLater(file_path, account.name, null); - db.close(); - - if (!isOnline(context) || (instantUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context))) { - return; - } - - // register for upload finishe message - // there is a litte problem with android API, we can register for - // particular - // intent in registerReceiver but we cannot unregister from precise - // intent - // we can unregister from entire listenings but thats suck a bit. - // On the other hand this might be only for dynamicly registered - // broadcast receivers, needs investigation. - /*IntentFilter filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE); - context.getApplicationContext().registerReceiver(this, filter);*/ - - 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); - context.startService(i); - - } - - private void handleConnectivityAction(Context context, Intent intent) { - if (!instantUploadEnabled(context)) { - Log_OC.d(TAG, "Instant upload disabled, abording uploading"); - return; - } - - if (!intent.hasExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY) - && isOnline(context) - && (!instantUploadViaWiFiOnly(context) || (instantUploadViaWiFiOnly(context) == isConnectedViaWiFi(context) == true))) { - DbHandler db = new DbHandler(context); - Cursor c = db.getAwaitingFiles(); - if (c.moveToFirst()) { - //IntentFilter filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE); - //context.getApplicationContext().registerReceiver(this, filter); - 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; - } - - public static boolean instantUploadEnabled(Context context) { - return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_uploading", false); - } - - public static boolean instantUploadViaWiFiOnly(Context context) { - return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_upload_on_wifi", false); - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/files/OwnCloudFileObserver.java b/src/de/mobilcom/debitel/cloud/android/files/OwnCloudFileObserver.java deleted file mode 100644 index 79f4c18a..00000000 --- a/src/de/mobilcom/debitel/cloud/android/files/OwnCloudFileObserver.java +++ /dev/null @@ -1,102 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.files; - -import java.io.File; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.datamodel.FileDataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult; -import de.mobilcom.debitel.cloud.android.operations.SynchronizeFileOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult.ResultCode; -import de.mobilcom.debitel.cloud.android.ui.activity.ConflictsResolveActivity; - - -import android.accounts.Account; -import android.content.Context; -import android.content.Intent; -import android.os.FileObserver; - -public class OwnCloudFileObserver extends FileObserver { - - public static int CHANGES_ONLY = CLOSE_WRITE; - - private static String TAG = OwnCloudFileObserver.class.getSimpleName(); - - private String mPath; - private int mMask; - private Account mOCAccount; - //private OCFile mFile; - private Context mContext; - - - public OwnCloudFileObserver(String path, Account account, Context context, int mask) { - super(path, mask); - if (path == null) - throw new IllegalArgumentException("NULL path argument received"); - /*if (file == null) - throw new IllegalArgumentException("NULL file argument received");*/ - if (account == null) - throw new IllegalArgumentException("NULL account argument received"); - if (context == null) - throw new IllegalArgumentException("NULL context argument received"); - /*if (!path.equals(file.getStoragePath()) && !path.equals(FileStorageUtils.getDefaultSavePathFor(account.name, file))) - throw new IllegalArgumentException("File argument is not linked to the local file set in path argument"); */ - mPath = path; - //mFile = file; - mOCAccount = account; - mContext = context; - mMask = mask; - } - - @Override - public void onEvent(int event, String path) { - Log_OC.d(TAG, "Got file modified with event " + event + " and path " + mPath + ((path != null) ? File.separator + path : "")); - if ((event & mMask) == 0) { - Log_OC.wtf(TAG, "Incorrect event " + event + " sent for file " + mPath + ((path != null) ? File.separator + path : "") + - " with registered for " + mMask + " and original path " + - mPath); - return; - } - FileDataStorageManager storageManager = new FileDataStorageManager(mOCAccount, mContext.getContentResolver()); - OCFile file = storageManager.getFileByLocalPath(mPath); // a fresh object is needed; many things could have occurred to the file since it was registered to observe - // again, assuming that local files are linked to a remote file AT MOST, SOMETHING TO BE DONE; - SynchronizeFileOperation sfo = new SynchronizeFileOperation(file, - null, - storageManager, - mOCAccount, - true, - true, - mContext); - RemoteOperationResult result = sfo.execute(mOCAccount, mContext); - if (result.getCode() == ResultCode.SYNC_CONFLICT) { - // ISSUE 5: if the user is not running the app (this is a service!), 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_ACCOUNT, mOCAccount); - mContext.startActivity(i); - } - // TODO save other errors in some point where the user can inspect them later; - // or maybe just toast them; - // or nothing, very strange fails - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/files/managers/OCNotificationManager.java b/src/de/mobilcom/debitel/cloud/android/files/managers/OCNotificationManager.java deleted file mode 100644 index f1f57d22..00000000 --- a/src/de/mobilcom/debitel/cloud/android/files/managers/OCNotificationManager.java +++ /dev/null @@ -1,154 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.files.managers; - -import java.util.HashMap; -import java.util.Map; - -import android.app.Notification; -import android.app.NotificationManager; -import android.content.Context; -import android.widget.RemoteViews; - -import de.mobilcom.debitel.cloud.android.R; - -public class OCNotificationManager { - - enum NotificationType { - NOTIFICATION_SIMPLE, - NOTIFICATION_PROGRESS - } - - static public class NotificationData { - private String mText, mSubtitle; - private int mPercent; - private boolean mOngoing; - - public NotificationData(String text, String subtitle, boolean ongoing) { - this(text, subtitle, -1, ongoing); - } - - public NotificationData(int percent, boolean ongoing) { - this(null, null, percent, ongoing); - } - - public NotificationData(String text, int percent, boolean ongoing) { - this(text, null, percent, ongoing); - } - - public NotificationData(String text, String subtitle, int percent, boolean ongoing) { - mText = text; - mPercent = percent; - mSubtitle = subtitle; - mOngoing = ongoing; - } - - public String getText() { return mText; } - public int getPercent() { return mPercent; } - public String getSubtitle() { return mSubtitle; } - public boolean getOngoing() { return mOngoing; } - } - - static private OCNotificationManager mInstance = null; - - private class NotificationTypePair { - public Notification mNotificaiton; - public NotificationType mType; - public NotificationTypePair(Notification n, NotificationType type) { - mNotificaiton = n; - mType = type; - } - } - - private Context mContext; - private Map mNotificationMap; - private int mNotificationCounter; - NotificationManager mNM; - - static OCNotificationManager getInstance(Context context) { - if (mInstance == null) - mInstance = new OCNotificationManager(context); - return mInstance; - } - - OCNotificationManager(Context context) { - mContext = context; - mNotificationMap = new HashMap(); - mNM = (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE); - mNotificationCounter = 0; - } - - public int postNotification(NotificationType type, NotificationData data) { - mNotificationCounter++; - Notification notification = null; - - switch (type) { - case NOTIFICATION_SIMPLE: - notification = new Notification(R.drawable.icon, data.getText(), System.currentTimeMillis()); - break; - case NOTIFICATION_PROGRESS: - notification = new Notification(); - notification.contentView = new RemoteViews(mContext.getPackageName(), R.layout.progressbar_layout); - notification.contentView.setTextViewText(R.id.status_text, - data.getText()); - notification.contentView.setImageViewResource(R.id.status_icon, - R.id.icon); - notification.contentView.setProgressBar(R.id.status_progress, - 100, - data.getPercent(), - false); - break; - default: - return -1; - } - if (data.getOngoing()) { - notification.flags |= notification.flags | Notification.FLAG_ONGOING_EVENT; - } - - mNotificationMap.put(mNotificationCounter, new NotificationTypePair(notification, type)); - return mNotificationCounter; - } - - public boolean updateNotification(int notification_id, NotificationData data) { - if (!mNotificationMap.containsKey(notification_id)) { - return false; - } - NotificationTypePair pair = mNotificationMap.get(notification_id); - switch (pair.mType) { - case NOTIFICATION_PROGRESS: - pair.mNotificaiton.contentView.setProgressBar(R.id.status_text, - 100, - data.getPercent(), - false); - return true; - case NOTIFICATION_SIMPLE: - pair.mNotificaiton = new Notification(R.drawable.icon, - data.getText(), System.currentTimeMillis()); - mNM.notify(notification_id, pair.mNotificaiton); - return true; - default: - return false; - } - } - - public void discardNotification(int notification_id) { - mNM.cancel(notification_id); - mNotificationMap.remove(notification_id); - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/files/services/FileDownloader.java b/src/de/mobilcom/debitel/cloud/android/files/services/FileDownloader.java deleted file mode 100644 index b0040703..00000000 --- a/src/de/mobilcom/debitel/cloud/android/files/services/FileDownloader.java +++ /dev/null @@ -1,548 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.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 eu.alefzero.webdav.OnDatatransferProgressListener; - - -import android.accounts.Account; -import android.accounts.AccountsException; -import android.app.Notification; -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.widget.RemoteViews; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.authentication.AuthenticatorActivity; -import de.mobilcom.debitel.cloud.android.datamodel.FileDataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.network.OwnCloudClientUtils; -import de.mobilcom.debitel.cloud.android.operations.DownloadFileOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult.ResultCode; -import de.mobilcom.debitel.cloud.android.ui.activity.FileActivity; -import de.mobilcom.debitel.cloud.android.ui.activity.FileDisplayActivity; -import de.mobilcom.debitel.cloud.android.ui.preview.PreviewImageActivity; -import de.mobilcom.debitel.cloud.android.ui.preview.PreviewImageFragment; -import eu.alefzero.webdav.WebdavClient; - -public class FileDownloader extends Service implements OnDatatransferProgressListener { - - public static final String EXTRA_ACCOUNT = "ACCOUNT"; - public static final String EXTRA_FILE = "FILE"; - - private static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED"; - private static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH"; - public static final String EXTRA_DOWNLOAD_RESULT = "RESULT"; - public static final String EXTRA_FILE_PATH = "FILE_PATH"; - public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH"; - public static final String ACCOUNT_NAME = "ACCOUNT_NAME"; - - private static final String TAG = "FileDownloader"; - - private Looper mServiceLooper; - private ServiceHandler mServiceHandler; - private IBinder mBinder; - private WebdavClient mDownloadClient = null; - private Account mLastAccount = null; - private FileDataStorageManager mStorageManager; - - private ConcurrentMap mPendingDownloads = new ConcurrentHashMap(); - private DownloadFileOperation mCurrentDownload = null; - - private NotificationManager mNotificationManager; - private Notification mNotification; - private int mLastPercent; - - - public String getDownloadAddedMessage() { - return getClass().getName().toString() + DOWNLOAD_ADDED_MESSAGE; - } - - public String getDownloadFinishMessage() { - return getClass().getName().toString() + DOWNLOAD_FINISH_MESSAGE; - } - - /** - * Builds a key for mPendingDownloads from the account and file to download - * - * @param account Account where the file to download is stored - * @param file File to download - */ - private String buildRemoteName(Account account, OCFile file) { - return account.name + file.getRemotePath(); - } - - - /** - * Service initialization - */ - @Override - public void onCreate() { - super.onCreate(); - mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - HandlerThread thread = new HandlerThread("FileDownloaderThread", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - mServiceLooper = thread.getLooper(); - mServiceHandler = new ServiceHandler(mServiceLooper, this); - mBinder = new FileDownloaderBinder(); - } - - /** - * Entry point to add one or several files to the queue of downloads. - * - * New downloads 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) { - if ( !intent.hasExtra(EXTRA_ACCOUNT) || - !intent.hasExtra(EXTRA_FILE) - /*!intent.hasExtra(EXTRA_FILE_PATH) || - !intent.hasExtra(EXTRA_REMOTE_PATH)*/ - ) { - Log_OC.e(TAG, "Not enough information provided in intent"); - return START_NOT_STICKY; - } - Account account = intent.getParcelableExtra(EXTRA_ACCOUNT); - OCFile file = intent.getParcelableExtra(EXTRA_FILE); - - AbstractList requestedDownloads = new Vector(); // dvelasco: now this always contains just one element, but that can change in a near future (download of multiple selection) - String downloadKey = buildRemoteName(account, file); - try { - DownloadFileOperation newDownload = new DownloadFileOperation(account, file); - mPendingDownloads.putIfAbsent(downloadKey, newDownload); - newDownload.addDatatransferProgressListener(this); - newDownload.addDatatransferProgressListener((FileDownloaderBinder)mBinder); - requestedDownloads.add(downloadKey); - sendBroadcastNewDownload(newDownload); - - } catch (IllegalArgumentException e) { - Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage()); - return START_NOT_STICKY; - } - - if (requestedDownloads.size() > 0) { - Message msg = mServiceHandler.obtainMessage(); - msg.arg1 = startId; - msg.obj = requestedDownloads; - mServiceHandler.sendMessage(msg); - } - - return START_NOT_STICKY; - } - - - /** - * Provides a binder object that clients can use to perform operations on the queue of downloads, excepting the addition of new files. - * - * Implemented to perform cancellation, pause and resume of existing downloads. - */ - @Override - public IBinder onBind(Intent arg0) { - return mBinder; - } - - - /** - * Called when ALL the bound clients were onbound. - */ - @Override - public boolean onUnbind(Intent intent) { - ((FileDownloaderBinder)mBinder).clearListeners(); - return false; // not accepting rebinding (default behaviour) - } - - - /** - * Binder to let client components to perform operations on the queue of downloads. - * - * It provides by itself the available operations. - */ - public class FileDownloaderBinder extends Binder implements OnDatatransferProgressListener { - - /** - * Map of listeners that will be reported about progress of downloads from a {@link FileDownloaderBinder} instance - */ - private Map mBoundListeners = new HashMap(); - - - /** - * Cancels a pending or current download of a remote file. - * - * @param account Owncloud account where the remote file is stored. - * @param file A file in the queue of pending downloads - */ - public void cancel(Account account, OCFile file) { - DownloadFileOperation download = null; - synchronized (mPendingDownloads) { - download = mPendingDownloads.remove(buildRemoteName(account, file)); - } - if (download != null) { - download.cancel(); - } - } - - - public void clearListeners() { - mBoundListeners.clear(); - } - - - /** - * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download. - * - * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download. - * - * @param account Owncloud account where the remote file is stored. - * @param file A file that could be in the queue of downloads. - */ - public boolean isDownloading(Account account, OCFile file) { - if (account == null || file == null) return false; - String targetKey = buildRemoteName(account, file); - synchronized (mPendingDownloads) { - if (file.isDirectory()) { - // this can be slow if there are many downloads :( - Iterator it = mPendingDownloads.keySet().iterator(); - boolean found = false; - while (it.hasNext() && !found) { - found = it.next().startsWith(targetKey); - } - return found; - } else { - return (mPendingDownloads.containsKey(targetKey)); - } - } - } - - - /** - * Adds a listener interested in the progress of the download 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 download 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) { - // old way, should not be in use any more - } - - - @Override - public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, - String fileName) { - String key = buildRemoteName(mCurrentDownload.getAccount(), mCurrentDownload.getFile()); - OnDatatransferProgressListener boundListener = mBoundListeners.get(key); - if (boundListener != null) { - boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName); - } - } - - } - - - /** - * Download worker. Performs the pending downloads 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 - FileDownloader mService; - public ServiceHandler(Looper looper, FileDownloader 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 requestedDownloads = (AbstractList) msg.obj; - if (msg.obj != null) { - Iterator it = requestedDownloads.iterator(); - while (it.hasNext()) { - mService.downloadFile(it.next()); - } - } - mService.stopSelf(msg.arg1); - } - } - - - /** - * Core download method: requests a file to download and stores it. - * - * @param downloadKey Key to access the download to perform, contained in mPendingDownloads - */ - private void downloadFile(String downloadKey) { - - synchronized(mPendingDownloads) { - mCurrentDownload = mPendingDownloads.get(downloadKey); - } - - if (mCurrentDownload != null) { - - notifyDownloadStart(mCurrentDownload); - - RemoteOperationResult downloadResult = null; - try { - /// prepare client object to send the request to the ownCloud server - if (mDownloadClient == null || !mLastAccount.equals(mCurrentDownload.getAccount())) { - mLastAccount = mCurrentDownload.getAccount(); - mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver()); - mDownloadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext()); - } - - /// perform the download - downloadResult = mCurrentDownload.execute(mDownloadClient); - if (downloadResult.isSuccess()) { - saveDownloadedFile(); - } - - } catch (AccountsException e) { - Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e); - downloadResult = new RemoteOperationResult(e); - } catch (IOException e) { - Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e); - downloadResult = new RemoteOperationResult(e); - - } finally { - synchronized(mPendingDownloads) { - mPendingDownloads.remove(downloadKey); - } - } - - - /// notify result - notifyDownloadResult(mCurrentDownload, downloadResult); - - sendBroadcastDownloadFinished(mCurrentDownload, downloadResult); - } - } - - - /** - * Updates the OC File after a successful download. - */ - private void saveDownloadedFile() { - OCFile file = mCurrentDownload.getFile(); - long syncDate = System.currentTimeMillis(); - file.setLastSyncDateForProperties(syncDate); - file.setLastSyncDateForData(syncDate); - file.setModificationTimestamp(mCurrentDownload.getModificationTimestamp()); - file.setModificationTimestampAtLastSyncForData(mCurrentDownload.getModificationTimestamp()); - // file.setEtag(mCurrentDownload.getEtag()); // TODO Etag, where available - file.setMimetype(mCurrentDownload.getMimeType()); - file.setStoragePath(mCurrentDownload.getSavePath()); - file.setFileLength((new File(mCurrentDownload.getSavePath()).length())); - mStorageManager.saveFile(file); - } - - - /** - * Creates a status notification to show the download progress - * - * @param download Download operation starting. - */ - private void notifyDownloadStart(DownloadFileOperation download) { - /// create status notification with a progress bar - mLastPercent = 0; - mNotification = new Notification(R.drawable.icon, getString(R.string.downloader_download_in_progress_ticker), System.currentTimeMillis()); - mNotification.flags |= Notification.FLAG_ONGOING_EVENT; - mNotification.contentView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.progressbar_layout); - mNotification.contentView.setProgressBar(R.id.status_progress, 100, 0, download.getSize() < 0); - mNotification.contentView.setTextViewText(R.id.status_text, String.format(getString(R.string.downloader_download_in_progress_content), 0, new File(download.getSavePath()).getName())); - mNotification.contentView.setImageViewResource(R.id.status_icon, R.drawable.icon); - - /// includes a pending intent in the notification showing the details view of the file - Intent showDetailsIntent = null; - if (PreviewImageFragment.canBePreviewed(download.getFile())) { - showDetailsIntent = new Intent(this, PreviewImageActivity.class); - } else { - showDetailsIntent = new Intent(this, FileDisplayActivity.class); - } - showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, download.getFile()); - showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, download.getAccount()); - showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), showDetailsIntent, 0); - - mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotification); - } - - - /** - * Callback method to update the progress bar in the status notification. - */ - @Override - public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String fileName) { - int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer)); - if (percent != mLastPercent) { - mNotification.contentView.setProgressBar(R.id.status_progress, 100, percent, totalToTransfer < 0); - String text = String.format(getString(R.string.downloader_download_in_progress_content), percent, fileName); - mNotification.contentView.setTextViewText(R.id.status_text, text); - mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotification); - } - mLastPercent = percent; - } - - - /** - * Callback method to update the progress bar in the status notification (old version) - */ - @Override - public void onTransferProgress(long progressRate) { - // NOTHING TO DO HERE ANYMORE - } - - - /** - * Updates the status notification with the result of a download operation. - * - * @param downloadResult Result of the download operation. - * @param download Finished download operation - */ - private void notifyDownloadResult(DownloadFileOperation download, RemoteOperationResult downloadResult) { - mNotificationManager.cancel(R.string.downloader_download_in_progress_ticker); - if (!downloadResult.isCancelled()) { - int tickerId = (downloadResult.isSuccess()) ? R.string.downloader_download_succeeded_ticker : R.string.downloader_download_failed_ticker; - int contentId = (downloadResult.isSuccess()) ? R.string.downloader_download_succeeded_content : R.string.downloader_download_failed_content; - Notification finalNotification = new Notification(R.drawable.icon, getString(tickerId), System.currentTimeMillis()); - finalNotification.flags |= Notification.FLAG_AUTO_CANCEL; - boolean needsToUpdateCredentials = (downloadResult.getCode() == ResultCode.UNAUTHORIZED || - // (downloadResult.isTemporalRedirection() && downloadResult.isIdPRedirection() - (downloadResult.isIdPRedirection() - && MainApp.getAuthTokenTypeSamlSessionCookie().equals(mDownloadClient.getAuthTokenType()))); - if (needsToUpdateCredentials) { - // let the user update credentials with one click - Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class); - updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, download.getAccount()); - updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ENFORCED_UPDATE, true); - updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_TOKEN); - updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND); - finalNotification.contentIntent = PendingIntent.getActivity(this, (int)System.currentTimeMillis(), updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT); - finalNotification.setLatestEventInfo( getApplicationContext(), - getString(tickerId), - String.format(getString(contentId), new File(download.getSavePath()).getName()), - finalNotification.contentIntent); - mDownloadClient = null; // grant that future retries on the same account will get the fresh credentials - - } else { - Intent showDetailsIntent = null; - if (downloadResult.isSuccess()) { - if (PreviewImageFragment.canBePreviewed(download.getFile())) { - showDetailsIntent = new Intent(this, PreviewImageActivity.class); - } else { - showDetailsIntent = new Intent(this, FileDisplayActivity.class); - } - showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, download.getFile()); - showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, download.getAccount()); - showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - - } else { - // TODO put something smart in showDetailsIntent - showDetailsIntent = new Intent(); - } - finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), showDetailsIntent, 0); - finalNotification.setLatestEventInfo(getApplicationContext(), getString(tickerId), String.format(getString(contentId), new File(download.getSavePath()).getName()), finalNotification.contentIntent); - } - mNotificationManager.notify(tickerId, finalNotification); - } - } - - - /** - * Sends a broadcast when a download finishes in order to the interested activities can update their view - * - * @param download Finished download operation - * @param downloadResult Result of the download operation - */ - private void sendBroadcastDownloadFinished(DownloadFileOperation download, RemoteOperationResult downloadResult) { - Intent end = new Intent(getDownloadFinishMessage()); - end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess()); - end.putExtra(ACCOUNT_NAME, download.getAccount().name); - end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath()); - end.putExtra(EXTRA_FILE_PATH, download.getSavePath()); - sendStickyBroadcast(end); - } - - - /** - * Sends a broadcast when a new download is added to the queue. - * - * @param download Added download operation - */ - private void sendBroadcastNewDownload(DownloadFileOperation download) { - Intent added = new Intent(getDownloadAddedMessage()); - added.putExtra(ACCOUNT_NAME, download.getAccount().name); - added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath()); - added.putExtra(EXTRA_FILE_PATH, download.getSavePath()); - sendStickyBroadcast(added); - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/files/services/FileObserverService.java b/src/de/mobilcom/debitel/cloud/android/files/services/FileObserverService.java deleted file mode 100644 index 773faaa6..00000000 --- a/src/de/mobilcom/debitel/cloud/android/files/services/FileObserverService.java +++ /dev/null @@ -1,284 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.files.services; - -import java.io.File; -import java.util.HashMap; -import java.util.Map; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.datamodel.FileDataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.db.ProviderMeta.ProviderTableMeta; -import de.mobilcom.debitel.cloud.android.files.OwnCloudFileObserver; -import de.mobilcom.debitel.cloud.android.operations.SynchronizeFileOperation; -import de.mobilcom.debitel.cloud.android.utils.FileStorageUtils; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.database.Cursor; -import android.os.Binder; -import android.os.IBinder; - -public class FileObserverService extends Service { - - public final static int CMD_INIT_OBSERVED_LIST = 1; - public final static int CMD_ADD_OBSERVED_FILE = 2; - public final static int CMD_DEL_OBSERVED_FILE = 3; - - public final static String KEY_FILE_CMD = "KEY_FILE_CMD"; - public final static String KEY_CMD_ARG_FILE = "KEY_CMD_ARG_FILE"; - public final static String KEY_CMD_ARG_ACCOUNT = "KEY_CMD_ARG_ACCOUNT"; - - private static String TAG = FileObserverService.class.getSimpleName(); - - private static Map mObserversMap; - private static DownloadCompletedReceiverBis mDownloadReceiver; - private IBinder mBinder = new LocalBinder(); - - private String mDownloadAddedMessage; - private String mDownloadFinishMessage; - - public class LocalBinder extends Binder { - FileObserverService getService() { - return FileObserverService.this; - } - } - - @Override - public void onCreate() { - super.onCreate(); - mDownloadReceiver = new DownloadCompletedReceiverBis(); - - FileDownloader downloader = new FileDownloader(); - mDownloadAddedMessage = downloader.getDownloadAddedMessage(); - mDownloadFinishMessage= downloader.getDownloadFinishMessage(); - - IntentFilter filter = new IntentFilter(); - filter.addAction(mDownloadAddedMessage); - filter.addAction(mDownloadFinishMessage); - registerReceiver(mDownloadReceiver, filter); - - mObserversMap = new HashMap(); - //initializeObservedList(); - } - - - @Override - public void onDestroy() { - super.onDestroy(); - unregisterReceiver(mDownloadReceiver); - mObserversMap = null; // TODO study carefully the life cycle of Services to grant the best possible observance - Log_OC.d(TAG, "Bye, bye"); - } - - - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - // this occurs when system tries to restart - // service, so we need to reinitialize observers - if (intent == null) { - initializeObservedList(); - return Service.START_STICKY; - } - - if (!intent.hasExtra(KEY_FILE_CMD)) { - Log_OC.e(TAG, "No KEY_FILE_CMD argument given"); - return Service.START_STICKY; - } - - switch (intent.getIntExtra(KEY_FILE_CMD, -1)) { - case CMD_INIT_OBSERVED_LIST: - initializeObservedList(); - break; - case CMD_ADD_OBSERVED_FILE: - addObservedFile( (OCFile)intent.getParcelableExtra(KEY_CMD_ARG_FILE), - (Account)intent.getParcelableExtra(KEY_CMD_ARG_ACCOUNT)); - break; - case CMD_DEL_OBSERVED_FILE: - removeObservedFile( (OCFile)intent.getParcelableExtra(KEY_CMD_ARG_FILE), - (Account)intent.getParcelableExtra(KEY_CMD_ARG_ACCOUNT)); - break; - default: - Log_OC.wtf(TAG, "Incorrect key given"); - } - - return Service.START_STICKY; - } - - - /** - * Read from the local database the list of files that must to be kept synchronized and - * starts file observers to monitor local changes on them - */ - private void initializeObservedList() { - mObserversMap.clear(); - Cursor c = getContentResolver().query( - ProviderTableMeta.CONTENT_URI, - null, - ProviderTableMeta.FILE_KEEP_IN_SYNC + " = ?", - new String[] {String.valueOf(1)}, - null); - if (c == null || !c.moveToFirst()) return; - AccountManager acm = AccountManager.get(this); - Account[] accounts = acm.getAccounts(); - do { - Account account = null; - for (Account a : accounts) - if (a.name.equals(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_ACCOUNT_OWNER)))) { - account = a; - break; - } - - if (account == null) continue; - FileDataStorageManager storage = - new FileDataStorageManager(account, getContentResolver()); - if (!storage.fileExists(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH)))) - continue; - - String path = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH)); - if (path == null || path.length() <= 0) - continue; - OwnCloudFileObserver observer = - new OwnCloudFileObserver( path, - account, - getApplicationContext(), - OwnCloudFileObserver.CHANGES_ONLY); - mObserversMap.put(path, observer); - if (new File(path).exists()) { - observer.startWatching(); - Log_OC.d(TAG, "Started watching file " + path); - } - - } while (c.moveToNext()); - c.close(); - } - - - /** - * Registers the local copy of a remote file to be observed for local changes, - * an automatically updated in the ownCloud server. - * - * This method does NOT perform a {@link SynchronizeFileOperation} over the file. - * - * TODO We are ignoring that, currently, a local file can be linked to different files - * in ownCloud if it's uploaded several times. That's something pending to update: we - * will avoid that the same local file is linked to different remote files. - * - * @param file Object representing a remote file which local copy must be observed. - * @param account OwnCloud account containing file. - */ - private void addObservedFile(OCFile file, Account account) { - if (file == null) { - Log_OC.e(TAG, "Trying to add a NULL file to observer"); - return; - } - String localPath = file.getStoragePath(); - if (localPath == null || localPath.length() <= 0) { // file downloading / to be download for the first time - localPath = FileStorageUtils.getDefaultSavePathFor(account.name, file); - } - OwnCloudFileObserver observer = mObserversMap.get(localPath); - if (observer == null) { - /// the local file was never registered to observe before - observer = new OwnCloudFileObserver( localPath, - account, - getApplicationContext(), - OwnCloudFileObserver.CHANGES_ONLY); - mObserversMap.put(localPath, observer); - Log_OC.d(TAG, "Observer added for path " + localPath); - - if (file.isDown()) { - observer.startWatching(); - Log_OC.d(TAG, "Started watching " + localPath); - } // else - the observance can't be started on a file not already down; mDownloadReceiver will get noticed when the download of the file finishes - } - - } - - - /** - * Unregisters the local copy of a remote file to be observed for local changes. - * - * Starts to watch it, if the file has a local copy to watch. - * - * TODO We are ignoring that, currently, a local file can be linked to different files - * in ownCloud if it's uploaded several times. That's something pending to update: we - * will avoid that the same local file is linked to different remote files. - * - * @param file Object representing a remote file which local copy must be not observed longer. - * @param account OwnCloud account containing file. - */ - private void removeObservedFile(OCFile file, Account account) { - if (file == null) { - Log_OC.e(TAG, "Trying to remove a NULL file"); - return; - } - String localPath = file.getStoragePath(); - if (localPath == null || localPath.length() <= 0) { - localPath = FileStorageUtils.getDefaultSavePathFor(account.name, file); - } - - OwnCloudFileObserver observer = mObserversMap.get(localPath); - if (observer != null) { - observer.stopWatching(); - mObserversMap.remove(observer); - Log_OC.d(TAG, "Stopped watching " + localPath); - } - - } - - - /** - * Private receiver listening to events broadcast by the FileDownloader service. - * - * Starts and stops the observance on registered files when they are being download, - * in order to avoid to start unnecessary synchronizations. - */ - private class DownloadCompletedReceiverBis extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - String downloadPath = intent.getStringExtra(FileDownloader.EXTRA_FILE_PATH); - OwnCloudFileObserver observer = mObserversMap.get(downloadPath); - if (observer != null) { - if (intent.getAction().equals(mDownloadFinishMessage) && - new File(downloadPath).exists()) { // the download could be successful. not; in both cases, the file could be down, due to a former download or upload - observer.startWatching(); - Log_OC.d(TAG, "Watching again " + downloadPath); - - } else if (intent.getAction().equals(mDownloadAddedMessage)) { - observer.stopWatching(); - Log_OC.d(TAG, "Disabling observance of " + downloadPath); - } - } - } - - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/files/services/FileUploader.java b/src/de/mobilcom/debitel/cloud/android/files/services/FileUploader.java deleted file mode 100644 index 26f29ba4..00000000 --- a/src/de/mobilcom/debitel/cloud/android/files/services/FileUploader.java +++ /dev/null @@ -1,923 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.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 org.apache.http.HttpStatus; -import org.apache.jackrabbit.webdav.DavConstants; -import org.apache.jackrabbit.webdav.MultiStatus; -import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; - - -import eu.alefzero.webdav.OnDatatransferProgressListener; -import eu.alefzero.webdav.WebdavEntry; -import eu.alefzero.webdav.WebdavUtils; - - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AccountsException; -import android.app.Notification; -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.webkit.MimeTypeMap; -import android.widget.RemoteViews; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.authentication.AccountAuthenticator; -import de.mobilcom.debitel.cloud.android.authentication.AuthenticatorActivity; -import de.mobilcom.debitel.cloud.android.datamodel.FileDataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.db.DbHandler; -import de.mobilcom.debitel.cloud.android.network.OwnCloudClientUtils; -import de.mobilcom.debitel.cloud.android.operations.ChunkedUploadFileOperation; -import de.mobilcom.debitel.cloud.android.operations.CreateFolderOperation; -import de.mobilcom.debitel.cloud.android.operations.ExistenceCheckOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult; -import de.mobilcom.debitel.cloud.android.operations.UploadFileOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult.ResultCode; -import de.mobilcom.debitel.cloud.android.ui.activity.FailedUploadActivity; -import de.mobilcom.debitel.cloud.android.ui.activity.FileActivity; -import de.mobilcom.debitel.cloud.android.ui.activity.FileDisplayActivity; -import de.mobilcom.debitel.cloud.android.ui.activity.InstantUploadActivity; -import de.mobilcom.debitel.cloud.android.ui.preview.PreviewImageActivity; -import de.mobilcom.debitel.cloud.android.ui.preview.PreviewImageFragment; -import de.mobilcom.debitel.cloud.android.utils.OwnCloudVersion; - -import eu.alefzero.webdav.WebdavClient; - -public class FileUploader extends Service implements OnDatatransferProgressListener { - - 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 WebdavClient mUploadClient = null; - private Account mLastAccount = null; - private FileDataStorageManager mStorageManager; - - private ConcurrentMap mPendingUploads = new ConcurrentHashMap(); - private UploadFileOperation mCurrentUpload = null; - - private NotificationManager mNotificationManager; - private Notification mNotification; - private int mLastPercent; - private RemoteViews mDefaultNotificationContentView; - - - public String getUploadFinishMessage() { - return getClass().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; - } - - /** - * 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.i(TAG, "mPendingUploads size:" + mPendingUploads.size()); - 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(); - } - - /** - * 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) { - 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); - - 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] - : (String) null), storageManager); - if (files[i] == null) { - // TODO @andomaex add failure Notiification - return Service.START_NOT_STICKY; - } - } - } - - OwnCloudVersion ocv = new OwnCloudVersion(AccountManager.get(this).getUserData(account, - AccountAuthenticator.KEY_OC_VERSION)); - 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()); - if (chunked) { - newUpload = new ChunkedUploadFileOperation(account, files[i], isInstant, forceOverwrite, - localAction); - } else { - newUpload = new UploadFileOperation(account, files[i], isInstant, forceOverwrite, localAction); - } - if (isInstant) { - newUpload.setRemoteFolderToBeCreated(); - } - mPendingUploads.putIfAbsent(uploadKey, newUpload); // Grants that the file only upload once time - - 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) - } - - - /** - * 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 = null; - synchronized (mPendingUploads) { - upload = mPendingUploads.remove(buildRemoteName(account, file)); - } - if (upload != null) { - upload.cancel(); - } - } - - - - 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.isDirectory()) { - // 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) { - // old way, should not be in use any more - } - - - @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); - } - } - - } - - /** - * 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()); - } - } - 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) { - - 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()); - mUploadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext()); - } - - /// 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.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); - - } - - } - - /** - * 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 ExistenceCheckOperation(pathToGrant, this, false); - RemoteOperationResult result = operation.execute(mUploadClient); - if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND && mCurrentUpload.isRemoteFolderToBeCreated()) { - operation = new CreateFolderOperation( pathToGrant, - true, - mStorageManager ); - result = operation.execute(mUploadClient); - } - 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(); - long syncDate = System.currentTimeMillis(); - file.setLastSyncDateForData(syncDate); - - // / new PROPFIND to keep data consistent with server in theory, should - // return the same we already have - PropFindMethod propfind = null; - RemoteOperationResult result = null; - try { - propfind = new PropFindMethod(mUploadClient.getBaseUri() + WebdavUtils.encodePath(mCurrentUpload.getRemotePath()), - DavConstants.PROPFIND_ALL_PROP, - DavConstants.DEPTH_0); - int status = mUploadClient.executeMethod(propfind); - boolean isMultiStatus = (status == HttpStatus.SC_MULTI_STATUS); - if (isMultiStatus) { - MultiStatus resp = propfind.getResponseBodyAsMultiStatus(); - WebdavEntry we = new WebdavEntry(resp.getResponses()[0], mUploadClient.getBaseUri().getPath()); - updateOCFile(file, we); - file.setLastSyncDateForProperties(syncDate); - - } else { - mUploadClient.exhaustResponse(propfind.getResponseBodyAsStream()); - } - - result = new RemoteOperationResult(isMultiStatus, status, propfind.getResponseHeaders()); - Log_OC.i(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": " - + result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": " - + result.getLogMessage(), e); - - } finally { - if (propfind != null) - propfind.releaseConnection(); - } - - // / 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() - } - - mStorageManager.saveFile(file); - } - - private void updateOCFile(OCFile file, WebdavEntry we) { - file.setCreationTimestamp(we.createTimestamp()); - file.setFileLength(we.contentLength()); - file.setMimetype(we.contentType()); - file.setModificationTimestamp(we.modifiedTimestamp()); - file.setModificationTimestampAtLastSyncForData(we.modifiedTimestamp()); - // file.setEtag(mCurrentUpload.getEtag()); // TODO Etag, where available - } - - private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, - FileDataStorageManager storageManager) { - 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 - - // 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"; - } - newFile.setMimetype(mimeType); - - return newFile; - } - - /** - * Creates a status notification to show the upload progress - * - * @param upload Upload operation starting. - */ - @SuppressWarnings("deprecation") - private void notifyUploadStart(UploadFileOperation upload) { - // / create status notification with a progress bar - mLastPercent = 0; - mNotification = new Notification(R.drawable.icon, getString(R.string.uploader_upload_in_progress_ticker), - System.currentTimeMillis()); - mNotification.flags |= Notification.FLAG_ONGOING_EVENT; - mDefaultNotificationContentView = mNotification.contentView; - mNotification.contentView = new RemoteViews(getApplicationContext().getPackageName(), - R.layout.progressbar_layout); - mNotification.contentView.setProgressBar(R.id.status_progress, 100, 0, false); - mNotification.contentView.setTextViewText(R.id.status_text, - String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName())); - mNotification.contentView.setImageViewResource(R.id.status_icon, R.drawable.icon); - - /// 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); - mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), - (int) System.currentTimeMillis(), showDetailsIntent, 0); - - mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification); - } - - /** - * Callback method to update the progress bar in the status notification - */ - @Override - public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String fileName) { - int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer)); - if (percent != mLastPercent) { - mNotification.contentView.setProgressBar(R.id.status_progress, 100, percent, false); - String text = String.format(getString(R.string.uploader_upload_in_progress_content), percent, fileName); - mNotification.contentView.setTextViewText(R.id.status_text, text); - mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification); - } - mLastPercent = percent; - } - - /** - * Callback method to update the progress bar in the status notification - * (old version) - */ - @Override - public void onTransferProgress(long progressRate) { - // NOTHING TO DO HERE ANYMORE - } - - /** - * 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()); - if (uploadResult.isCancelled()) { - // / cancelled operation -> silent removal of progress notification - mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker); - - } else if (uploadResult.isSuccess()) { - // / success -> silent update of progress notification to success - // message - mNotification.flags ^= Notification.FLAG_ONGOING_EVENT; // remove - // the - // ongoing - // flag - mNotification.flags |= Notification.FLAG_AUTO_CANCEL; - mNotification.contentView = mDefaultNotificationContentView; - - /// includes a pending intent in the notification showing the details view of the file - Intent showDetailsIntent = null; - if (PreviewImageFragment.canBePreviewed(upload.getFile())) { - showDetailsIntent = new Intent(this, PreviewImageActivity.class); - } else { - 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); - mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), - (int) System.currentTimeMillis(), showDetailsIntent, 0); - - mNotification.setLatestEventInfo(getApplicationContext(), - getString(R.string.uploader_upload_succeeded_ticker), - String.format(getString(R.string.uploader_upload_succeeded_content_single), upload.getFileName()), - mNotification.contentIntent); - - mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification); // NOT - // AN - DbHandler db = new DbHandler(this.getBaseContext()); - db.removeIUPendingFile(mCurrentUpload.getOriginalStoragePath()); - db.close(); - - } else { - - // / fail -> explicit failure notification - mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker); - Notification finalNotification = new Notification(R.drawable.icon, - getString(R.string.uploader_upload_failed_ticker), System.currentTimeMillis()); - finalNotification.flags |= Notification.FLAG_AUTO_CANCEL; - String content = null; - - boolean needsToUpdateCredentials = (uploadResult.getCode() == ResultCode.UNAUTHORIZED || - //(uploadResult.isTemporalRedirection() && uploadResult.isIdPRedirection() && - (uploadResult.isIdPRedirection() && - MainApp.getAuthTokenTypeSamlSessionCookie().equals(mUploadClient.getAuthTokenType()))); - 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_ENFORCED_UPDATE, true); - updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_TOKEN); - updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND); - finalNotification.contentIntent = PendingIntent.getActivity(this, (int)System.currentTimeMillis(), updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT); - content = String.format(getString(R.string.uploader_upload_failed_content_single), upload.getFileName()); - finalNotification.setLatestEventInfo(getApplicationContext(), - getString(R.string.uploader_upload_failed_ticker), content, finalNotification.contentIntent); - mUploadClient = null; // grant that future retries on the same account will get the fresh credentials - } else { - // TODO put something smart in the contentIntent below - // finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); - //} - - if (uploadResult.getCode() == ResultCode.LOCAL_STORAGE_FULL - || uploadResult.getCode() == ResultCode.LOCAL_STORAGE_NOT_COPIED) { - // TODO we need a class to provide error messages for the users - // from a RemoteOperationResult and a RemoteOperation - content = String.format(getString(R.string.error__upload__local_file_not_copied), upload.getFileName(), - getString(R.string.app_name)); - } else if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { - content = getString(R.string.failed_upload_quota_exceeded_text); - } else { - content = String - .format(getString(R.string.uploader_upload_failed_content_single), upload.getFileName()); - } - - // we add only for instant-uploads the InstantUploadActivity and the - // db entry - Intent detailUploadIntent = null; - if (upload.isInstant() && InstantUploadActivity.IS_ENABLED) { - detailUploadIntent = new Intent(this, InstantUploadActivity.class); - detailUploadIntent.putExtra(FileUploader.KEY_ACCOUNT, upload.getAccount()); - } else { - detailUploadIntent = new Intent(this, FailedUploadActivity.class); - detailUploadIntent.putExtra(FailedUploadActivity.MESSAGE, content); - } - finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), - (int) System.currentTimeMillis(), detailUploadIntent, PendingIntent.FLAG_UPDATE_CURRENT - | PendingIntent.FLAG_ONE_SHOT); - - 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(); - } - } - } - } - finalNotification.setLatestEventInfo(getApplicationContext(), - getString(R.string.uploader_upload_failed_ticker), content, finalNotification.contentIntent); - - mNotificationManager.notify(R.string.uploader_upload_failed_ticker, finalNotification); - } - - } - - /** - * 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); - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/files/services/OnUploadCompletedListener.java b/src/de/mobilcom/debitel/cloud/android/files/services/OnUploadCompletedListener.java deleted file mode 100644 index 7f8039e0..00000000 --- a/src/de/mobilcom/debitel/cloud/android/files/services/OnUploadCompletedListener.java +++ /dev/null @@ -1,26 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.files.services; - -public interface OnUploadCompletedListener extends Runnable { - - public boolean getUploadResult(); - - public void setUploadResult(boolean result); -} diff --git a/src/de/mobilcom/debitel/cloud/android/location/LocationServiceLauncherReciever.java b/src/de/mobilcom/debitel/cloud/android/location/LocationServiceLauncherReciever.java deleted file mode 100644 index 0c77a25a..00000000 --- a/src/de/mobilcom/debitel/cloud/android/location/LocationServiceLauncherReciever.java +++ /dev/null @@ -1,88 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.location; - -import de.mobilcom.debitel.cloud.android.Log_OC; - -import android.app.ActivityManager; -import android.app.ActivityManager.RunningServiceInfo; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -public class LocationServiceLauncherReciever extends BroadcastReceiver { - - private final String TAG = getClass().getSimpleName(); - - @Override - public void onReceive(Context context, Intent intent) { - Intent deviceTrackingIntent = new Intent(); - deviceTrackingIntent - .setAction("de.mobilcom.debitel.cloud.android.location.LocationUpdateService"); - SharedPreferences preferences = PreferenceManager - .getDefaultSharedPreferences(context); - boolean trackDevice = preferences.getBoolean("enable_devicetracking", - true); - - // Used in Preferences activity so that tracking is disabled or - // reenabled - if (intent.hasExtra("TRACKING_SETTING")) { - trackDevice = intent.getBooleanExtra("TRACKING_SETTING", true); - } - - startOrStopDeviceTracking(context, trackDevice); - } - - /** - * Used internally. Starts or stops the device tracking service - * - * @param trackDevice true to start the service, false to stop it - */ - private void startOrStopDeviceTracking(Context context, boolean trackDevice) { - Intent deviceTrackingIntent = new Intent(); - deviceTrackingIntent - .setAction("de.mobilcom.debitel.cloud.android.location.LocationUpdateService"); - if (!isDeviceTrackingServiceRunning(context) && trackDevice) { - Log_OC.d(TAG, "Starting device tracker service"); - context.startService(deviceTrackingIntent); - } else if (isDeviceTrackingServiceRunning(context) && !trackDevice) { - Log_OC.d(TAG, "Stopping device tracker service"); - context.stopService(deviceTrackingIntent); - } - } - - /** - * Checks to see whether or not the LocationUpdateService is running - * - * @return true, if it is. Otherwise false - */ - private boolean isDeviceTrackingServiceRunning(Context context) { - ActivityManager manager = (ActivityManager) context - .getSystemService(Context.ACTIVITY_SERVICE); - for (RunningServiceInfo service : manager - .getRunningServices(Integer.MAX_VALUE)) { - if (getClass().getName().equals(service.service.getClassName())) { - return true; - } - } - return false; - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/location/LocationUpdateService.java b/src/de/mobilcom/debitel/cloud/android/location/LocationUpdateService.java deleted file mode 100644 index dccfe7fe..00000000 --- a/src/de/mobilcom/debitel/cloud/android/location/LocationUpdateService.java +++ /dev/null @@ -1,111 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.location; - -import android.app.IntentService; -import android.content.Intent; -import android.content.SharedPreferences; -import android.location.Criteria; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.location.LocationProvider; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.widget.Toast; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; - -public class LocationUpdateService extends IntentService implements - LocationListener { - - public static final String TAG = "LocationUpdateService"; - - private LocationManager mLocationManager; - private LocationProvider mLocationProvider; - private SharedPreferences mPreferences; - - public LocationUpdateService() { - super(TAG); - } - - @Override - protected void onHandleIntent(Intent intent) { - mLocationManager = (LocationManager) getSystemService(LOCATION_SERVICE); - // Determine, how we can track the device - Criteria criteria = new Criteria(); - criteria.setAccuracy(Criteria.ACCURACY_FINE); - criteria.setPowerRequirement(Criteria.POWER_LOW); - mLocationProvider = mLocationManager.getProvider(mLocationManager - .getBestProvider(criteria, true)); - - // Notify user if there is no way to track the device - if (mLocationProvider == null) { - String message = String.format(getString(R.string.location_no_provider), getString(R.string.app_name)); - Toast.makeText(this, - message, - Toast.LENGTH_LONG).show(); - stopSelf(); - return; - } - - // Get preferences for device tracking - mPreferences = PreferenceManager.getDefaultSharedPreferences(this); - boolean trackDevice = mPreferences.getBoolean("enable_devicetracking", - true); - int updateIntervall = Integer.parseInt(mPreferences.getString( - "devicetracking_update_intervall", "30")) * 60 * 1000; - int distanceBetweenLocationChecks = 50; - - // If we do shall track the device -> Stop - if (!trackDevice) { - Log_OC.d(TAG, "Devicetracking is disabled"); - stopSelf(); - return; - } - - mLocationManager.requestLocationUpdates(mLocationProvider.getName(), - updateIntervall, distanceBetweenLocationChecks, this); - } - - @Override - public void onLocationChanged(Location location) { - Log_OC.d(TAG, "Location changed: " + location); - - } - - @Override - public void onProviderDisabled(String arg0) { - // TODO Auto-generated method stub - - } - - @Override - public void onProviderEnabled(String arg0) { - // TODO Auto-generated method stub - - } - - @Override - public void onStatusChanged(String arg0, int arg1, Bundle arg2) { - // TODO Auto-generated method stub - - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/media/MediaControlView.java b/src/de/mobilcom/debitel/cloud/android/media/MediaControlView.java deleted file mode 100644 index 5e24907a..00000000 --- a/src/de/mobilcom/debitel/cloud/android/media/MediaControlView.java +++ /dev/null @@ -1,472 +0,0 @@ -/* ownCloud Android client application - * - * 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 de.mobilcom.debitel.cloud.android.media; - -import android.content.Context; -import android.media.MediaPlayer; -import android.os.Handler; -import android.os.Message; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityNodeInfo; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.MediaController.MediaPlayerControl; -import android.widget.ProgressBar; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import java.util.Formatter; -import java.util.Locale; - -import de.mobilcom.debitel.cloud.android.R; - -/** - * View containing controls for a {@link MediaPlayer}. - * - * Holds buttons "play / pause", "rewind", "fast forward" - * and a progress slider. - * - * It synchronizes itself with the state of the - * {@link MediaPlayer}. - * - * @author David A. Velasco - */ - -public class MediaControlView extends FrameLayout /* implements OnLayoutChangeListener, OnTouchListener */ implements OnClickListener, OnSeekBarChangeListener { - - private MediaPlayerControl mPlayer; - private Context mContext; - private View mRoot; - private ProgressBar mProgress; - private TextView mEndTime, mCurrentTime; - private boolean mDragging; - private static final int SHOW_PROGRESS = 1; - StringBuilder mFormatBuilder; - Formatter mFormatter; - private ImageButton mPauseButton; - private ImageButton mFfwdButton; - private ImageButton mRewButton; - - public MediaControlView(Context context, AttributeSet attrs) { - super(context, attrs); - mContext = context; - - FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ); - LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mRoot = inflate.inflate(R.layout.media_control, null); - initControllerView(mRoot); - addView(mRoot, frameParams); - - setFocusable(true); - setFocusableInTouchMode(true); - setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); - requestFocus(); - } - - @Override - public void onFinishInflate() { - /* - if (mRoot != null) - initControllerView(mRoot); - */ - } - - /* TODO REMOVE - public MediaControlView(Context context, boolean useFastForward) { - super(context); - mContext = context; - mUseFastForward = useFastForward; - initFloatingWindowLayout(); - //initFloatingWindow(); - } - */ - - /* TODO REMOVE - public MediaControlView(Context context) { - this(context, true); - } - */ - - /* T - private void initFloatingWindow() { - mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); - mWindow = PolicyManager.makeNewWindow(mContext); - mWindow.setWindowManager(mWindowManager, null, null); - mWindow.requestFeature(Window.FEATURE_NO_TITLE); - mDecor = mWindow.getDecorView(); - mDecor.setOnTouchListener(mTouchListener); - mWindow.setContentView(this); - mWindow.setBackgroundDrawableResource(android.R.color.transparent); - - // While the media controller is up, the volume control keys should - // affect the media stream type - mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC); - - setFocusable(true); - setFocusableInTouchMode(true); - setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); - requestFocus(); - } - */ - - /* - // Allocate and initialize the static parts of mDecorLayoutParams. Must - // also call updateFloatingWindowLayout() to fill in the dynamic parts - // (y and width) before mDecorLayoutParams can be used. - private void initFloatingWindowLayout() { - mDecorLayoutParams = new WindowManager.LayoutParams(); - WindowManager.LayoutParams p = mDecorLayoutParams; - p.gravity = Gravity.TOP; - p.height = LayoutParams.WRAP_CONTENT; - p.x = 0; - p.format = PixelFormat.TRANSLUCENT; - p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; - p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; - p.token = null; - p.windowAnimations = 0; // android.R.style.DropDownAnimationDown; - } - */ - - // Update the dynamic parts of mDecorLayoutParams - // Must be called with mAnchor != NULL. - /* - private void updateFloatingWindowLayout() { - int [] anchorPos = new int[2]; - mAnchor.getLocationOnScreen(anchorPos); - - WindowManager.LayoutParams p = mDecorLayoutParams; - p.width = mAnchor.getWidth(); - p.y = anchorPos[1] + mAnchor.getHeight(); - } - */ - - /* - // This is called whenever mAnchor's layout bound changes - public void onLayoutChange(View v, int left, int top, int right, - int bottom, int oldLeft, int oldTop, int oldRight, - int oldBottom) { - //updateFloatingWindowLayout(); - if (mShowing) { - mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams); - } - } - */ - - /* - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (mShowing) { - hide(); - } - } - return false; - } - */ - - - public void setMediaPlayer(MediaPlayerControl player) { - mPlayer = player; - mHandler.sendEmptyMessage(SHOW_PROGRESS); - updatePausePlay(); - } - - - private void initControllerView(View v) { - mPauseButton = (ImageButton) v.findViewById(R.id.playBtn); - if (mPauseButton != null) { - mPauseButton.requestFocus(); - mPauseButton.setOnClickListener(this); - } - - mFfwdButton = (ImageButton) v.findViewById(R.id.forwardBtn); - if (mFfwdButton != null) { - mFfwdButton.setOnClickListener(this); - } - - mRewButton = (ImageButton) v.findViewById(R.id.rewindBtn); - if (mRewButton != null) { - mRewButton.setOnClickListener(this); - } - - mProgress = (ProgressBar) v.findViewById(R.id.progressBar); - if (mProgress != null) { - if (mProgress instanceof SeekBar) { - SeekBar seeker = (SeekBar) mProgress; - seeker.setOnSeekBarChangeListener(this); - } - mProgress.setMax(1000); - } - - mEndTime = (TextView) v.findViewById(R.id.totalTimeText); - mCurrentTime = (TextView) v.findViewById(R.id.currentTimeText); - mFormatBuilder = new StringBuilder(); - mFormatter = new Formatter(mFormatBuilder, Locale.getDefault()); - - } - - - /** - * Disable pause or seek buttons if the stream cannot be paused or seeked. - * This requires the control interface to be a MediaPlayerControlExt - */ - private void disableUnsupportedButtons() { - try { - if (mPauseButton != null && !mPlayer.canPause()) { - mPauseButton.setEnabled(false); - } - if (mRewButton != null && !mPlayer.canSeekBackward()) { - mRewButton.setEnabled(false); - } - if (mFfwdButton != null && !mPlayer.canSeekForward()) { - mFfwdButton.setEnabled(false); - } - } catch (IncompatibleClassChangeError ex) { - // We were given an old version of the interface, that doesn't have - // the canPause/canSeekXYZ methods. This is OK, it just means we - // assume the media can be paused and seeked, and so we don't disable - // the buttons. - } - } - - - private Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - int pos; - switch (msg.what) { - case SHOW_PROGRESS: - pos = setProgress(); - if (!mDragging) { - msg = obtainMessage(SHOW_PROGRESS); - sendMessageDelayed(msg, 1000 - (pos % 1000)); - } - break; - } - } - }; - - private String stringForTime(int timeMs) { - int totalSeconds = timeMs / 1000; - - int seconds = totalSeconds % 60; - int minutes = (totalSeconds / 60) % 60; - int hours = totalSeconds / 3600; - - mFormatBuilder.setLength(0); - if (hours > 0) { - return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString(); - } else { - return mFormatter.format("%02d:%02d", minutes, seconds).toString(); - } - } - - private int setProgress() { - if (mPlayer == null || mDragging) { - return 0; - } - int position = mPlayer.getCurrentPosition(); - int duration = mPlayer.getDuration(); - if (mProgress != null) { - if (duration > 0) { - // use long to avoid overflow - long pos = 1000L * position / duration; - mProgress.setProgress( (int) pos); - } - int percent = mPlayer.getBufferPercentage(); - mProgress.setSecondaryProgress(percent * 10); - } - - if (mEndTime != null) - mEndTime.setText(stringForTime(duration)); - if (mCurrentTime != null) - mCurrentTime.setText(stringForTime(position)); - - return position; - } - - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - int keyCode = event.getKeyCode(); - final boolean uniqueDown = event.getRepeatCount() == 0 - && event.getAction() == KeyEvent.ACTION_DOWN; - if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK - || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE - || keyCode == KeyEvent.KEYCODE_SPACE) { - if (uniqueDown) { - doPauseResume(); - //show(sDefaultTimeout); - if (mPauseButton != null) { - mPauseButton.requestFocus(); - } - } - return true; - } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { - if (uniqueDown && !mPlayer.isPlaying()) { - mPlayer.start(); - updatePausePlay(); - //show(sDefaultTimeout); - } - return true; - } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP - || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { - if (uniqueDown && mPlayer.isPlaying()) { - mPlayer.pause(); - updatePausePlay(); - //show(sDefaultTimeout); - } - return true; - } - - //show(sDefaultTimeout); - return super.dispatchKeyEvent(event); - } - - public void updatePausePlay() { - if (mRoot == null || mPauseButton == null) - return; - - if (mPlayer.isPlaying()) { - mPauseButton.setImageResource(android.R.drawable.ic_media_pause); - } else { - mPauseButton.setImageResource(android.R.drawable.ic_media_play); - } - } - - private void doPauseResume() { - if (mPlayer.isPlaying()) { - mPlayer.pause(); - } else { - mPlayer.start(); - } - updatePausePlay(); - } - - @Override - public void setEnabled(boolean enabled) { - if (mPauseButton != null) { - mPauseButton.setEnabled(enabled); - } - if (mFfwdButton != null) { - mFfwdButton.setEnabled(enabled); - } - if (mRewButton != null) { - mRewButton.setEnabled(enabled); - } - if (mProgress != null) { - mProgress.setEnabled(enabled); - } - disableUnsupportedButtons(); - super.setEnabled(enabled); - } - - @Override - public void onClick(View v) { - int pos; - boolean playing = mPlayer.isPlaying(); - switch (v.getId()) { - - case R.id.playBtn: - doPauseResume(); - break; - - case R.id.rewindBtn: - pos = mPlayer.getCurrentPosition(); - pos -= 5000; - mPlayer.seekTo(pos); - if (!playing) mPlayer.pause(); // necessary in some 2.3.x devices - setProgress(); - break; - - case R.id.forwardBtn: - pos = mPlayer.getCurrentPosition(); - pos += 15000; - mPlayer.seekTo(pos); - if (!playing) mPlayer.pause(); // necessary in some 2.3.x devices - setProgress(); - break; - - } - } - - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (!fromUser) { - // We're not interested in programmatically generated changes to - // the progress bar's position. - return; - } - - long duration = mPlayer.getDuration(); - long newposition = (duration * progress) / 1000L; - mPlayer.seekTo( (int) newposition); - if (mCurrentTime != null) - mCurrentTime.setText(stringForTime( (int) newposition)); - } - - /** - * Called in devices with touchpad when the user starts to adjust the - * position of the seekbar's thumb. - * - * Will be followed by several onProgressChanged notifications. - */ - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - mDragging = true; // monitors the duration of dragging - mHandler.removeMessages(SHOW_PROGRESS); // grants no more updates with media player progress while dragging - } - - - /** - * Called in devices with touchpad when the user finishes the - * adjusting of the seekbar. - */ - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - mDragging = false; - setProgress(); - updatePausePlay(); - mHandler.sendEmptyMessage(SHOW_PROGRESS); // grants future updates with media player progress - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - event.setClassName(MediaControlView.class.getName()); - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setClassName(MediaControlView.class.getName()); - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/media/MediaService.java b/src/de/mobilcom/debitel/cloud/android/media/MediaService.java deleted file mode 100644 index a5094cec..00000000 --- a/src/de/mobilcom/debitel/cloud/android/media/MediaService.java +++ /dev/null @@ -1,705 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.media; - -import android.accounts.Account; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.MediaPlayer.OnCompletionListener; -import android.media.MediaPlayer.OnErrorListener; -import android.media.MediaPlayer.OnPreparedListener; -import android.net.wifi.WifiManager; -import android.net.wifi.WifiManager.WifiLock; -import android.os.IBinder; -import android.os.PowerManager; -import android.widget.Toast; - -import java.io.IOException; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.ui.activity.FileActivity; -import de.mobilcom.debitel.cloud.android.ui.activity.FileDisplayActivity; - -/** - * Service that handles media playback, both audio and video. - * - * Waits for Intents which signal the service to perform specific operations: Play, Pause, - * Rewind, etc. - * - * @author David A. Velasco - */ -public class MediaService extends Service implements OnCompletionListener, OnPreparedListener, - OnErrorListener, AudioManager.OnAudioFocusChangeListener { - - private static final String TAG = MediaService.class.getSimpleName(); - - private static final String MY_PACKAGE = MediaService.class.getPackage() != null ? MediaService.class.getPackage().getName() : "de.mobilcom.debitel.cloud.android.media"; - - /// Intent actions that we are prepared to handle - public static final String ACTION_PLAY_FILE = MY_PACKAGE + ".action.PLAY_FILE"; - public static final String ACTION_STOP_ALL = MY_PACKAGE + ".action.STOP_ALL"; - - /// Keys to add extras to the action - public static final String EXTRA_FILE = MY_PACKAGE + ".extra.FILE"; - public static final String EXTRA_ACCOUNT = MY_PACKAGE + ".extra.ACCOUNT"; - public static String EXTRA_START_POSITION = MY_PACKAGE + ".extra.START_POSITION"; - public static final String EXTRA_PLAY_ON_LOAD = MY_PACKAGE + ".extra.PLAY_ON_LOAD"; - - - /** Error code for specific messages - see regular error codes at {@link MediaPlayer} */ - public static final int OC_MEDIA_ERROR = 0; - - /** Time To keep the control panel visible when the user does not use it */ - public static final int MEDIA_CONTROL_SHORT_LIFE = 4000; - - /** Time To keep the control panel visible when the user does not use it */ - public static final int MEDIA_CONTROL_PERMANENT = 0; - - /** Volume to set when audio focus is lost and ducking is allowed */ - private static final float DUCK_VOLUME = 0.1f; - - /** Media player instance */ - private MediaPlayer mPlayer = null; - - /** Reference to the system AudioManager */ - private AudioManager mAudioManager = null; - - - /** Values to indicate the state of the service */ - enum State { - STOPPED, - PREPARING, - PLAYING, - PAUSED - }; - - - /** Current state */ - private State mState = State.STOPPED; - - /** Possible focus values */ - enum AudioFocus { - NO_FOCUS, - NO_FOCUS_CAN_DUCK, - FOCUS - } - - /** Current focus state */ - private AudioFocus mAudioFocus = AudioFocus.NO_FOCUS; - - - /** 'True' when the current song is streaming from the network */ - private boolean mIsStreaming = false; - - /** Wifi lock kept to prevents the device from shutting off the radio when streaming a file. */ - private WifiLock mWifiLock; - - private static final String MEDIA_WIFI_LOCK_TAG = MY_PACKAGE + ".WIFI_LOCK"; - - /** Notification to keep in the notification bar while a song is playing */ - private NotificationManager mNotificationManager; - private Notification mNotification = null; - - /** File being played */ - private OCFile mFile; - - /** Account holding the file being played */ - private Account mAccount; - - /** Flag signaling if the audio should be played immediately when the file is prepared */ - protected boolean mPlayOnPrepared; - - /** Position, in miliseconds, where the audio should be started */ - private int mStartPosition; - - /** Interface to access the service through binding */ - private IBinder mBinder; - - /** Control panel shown to the user to control the playback, to register through binding */ - private MediaControlView mMediaController; - - - - /** - * Helper method to get an error message suitable to show to users for errors occurred in media playback, - * - * @param context A context to access string resources. - * @param what See {@link MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int) - * @param extra See {@link MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int) - * @return Message suitable to users. - */ - public static String getMessageForMediaError(Context context, int what, int extra) { - int messageId; - - if (what == OC_MEDIA_ERROR) { - messageId = extra; - - } else if (extra == MediaPlayer.MEDIA_ERROR_UNSUPPORTED) { - /* Added in API level 17 - Bitstream is conforming to the related coding standard or file spec, but the media framework does not support the feature. - Constant Value: -1010 (0xfffffc0e) - */ - messageId = R.string.media_err_unsupported; - - } else if (extra == MediaPlayer.MEDIA_ERROR_IO) { - /* Added in API level 17 - File or network related operation errors. - Constant Value: -1004 (0xfffffc14) - */ - messageId = R.string.media_err_io; - - } else if (extra == MediaPlayer.MEDIA_ERROR_MALFORMED) { - /* Added in API level 17 - Bitstream is not conforming to the related coding standard or file spec. - Constant Value: -1007 (0xfffffc11) - */ - messageId = R.string.media_err_malformed; - - } else if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) { - /* Added in API level 17 - Some operation takes too long to complete, usually more than 3-5 seconds. - Constant Value: -110 (0xffffff92) - */ - messageId = R.string.media_err_timeout; - - } else if (what == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { - /* Added in API level 3 - The video is streamed and its container is not valid for progressive playback i.e the video's index (e.g moov atom) is not at the start of the file. - Constant Value: 200 (0x000000c8) - */ - messageId = R.string.media_err_invalid_progressive_playback; - - } else { - /* MediaPlayer.MEDIA_ERROR_UNKNOWN - Added in API level 1 - Unspecified media player error. - Constant Value: 1 (0x00000001) - */ - /* MediaPlayer.MEDIA_ERROR_SERVER_DIED) - Added in API level 1 - Media server died. In this case, the application must release the MediaPlayer object and instantiate a new one. - Constant Value: 100 (0x00000064) - */ - messageId = R.string.media_err_unknown; - } - return context.getString(messageId); - } - - - - /** - * Initialize a service instance - * - * {@inheritDoc} - */ - @Override - public void onCreate() { - Log_OC.d(TAG, "Creating ownCloud media service"); - - mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)). - createWifiLock(WifiManager.WIFI_MODE_FULL, MEDIA_WIFI_LOCK_TAG); - - mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE); - mBinder = new MediaServiceBinder(this); - } - - - /** - * Entry point for Intents requesting actions, sent here via startService. - * - * {@inheritDoc} - */ - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - String action = intent.getAction(); - if (action.equals(ACTION_PLAY_FILE)) { - processPlayFileRequest(intent); - - } else if (action.equals(ACTION_STOP_ALL)) { - processStopRequest(true); - } - - return START_NOT_STICKY; // don't want it to restart in case it's killed. - } - - - /** - * Processes a request to play a media file received as a parameter - * - * TODO If a new request is received when a file is being prepared, it is ignored. Is this what we want? - * - * @param intent Intent received in the request with the data to identify the file to play. - */ - private void processPlayFileRequest(Intent intent) { - if (mState != State.PREPARING) { - mFile = intent.getExtras().getParcelable(EXTRA_FILE); - mAccount = intent.getExtras().getParcelable(EXTRA_ACCOUNT); - mPlayOnPrepared = intent.getExtras().getBoolean(EXTRA_PLAY_ON_LOAD, false); - mStartPosition = intent.getExtras().getInt(EXTRA_START_POSITION, 0); - tryToGetAudioFocus(); - playMedia(); - } - } - - - /** - * Processes a request to play a media file. - */ - protected void processPlayRequest() { - // request audio focus - tryToGetAudioFocus(); - - // actually play the song - if (mState == State.STOPPED) { - // (re)start playback - playMedia(); - - } else if (mState == State.PAUSED) { - // continue playback - mState = State.PLAYING; - setUpAsForeground(String.format(getString(R.string.media_state_playing), mFile.getFileName())); - configAndStartMediaPlayer(); - - } - } - - - /** - * Makes sure the media player exists and has been reset. This will create the media player - * if needed. reset the existing media player if one already exists. - */ - protected void createMediaPlayerIfNeeded() { - if (mPlayer == null) { - mPlayer = new MediaPlayer(); - - // make sure the CPU won't go to sleep while media is playing - mPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); - - // the media player will notify the service when it's ready preparing, and when it's done playing - mPlayer.setOnPreparedListener(this); - mPlayer.setOnCompletionListener(this); - mPlayer.setOnErrorListener(this); - - } else { - mPlayer.reset(); - } - } - - /** - * Processes a request to pause the current playback - */ - protected void processPauseRequest() { - if (mState == State.PLAYING) { - mState = State.PAUSED; - mPlayer.pause(); - releaseResources(false); // retain media player in pause - // TODO polite audio focus, instead of keep it owned; or not? - } - } - - - /** - * Processes a request to stop the playback. - * - * @param force When 'true', the playback is stopped no matter the value of mState - */ - protected void processStopRequest(boolean force) { - if (mState != State.PREPARING || force) { - mState = State.STOPPED; - mFile = null; - mAccount = null; - releaseResources(true); - giveUpAudioFocus(); - stopSelf(); // service is no longer necessary - } - } - - - /** - * Releases resources used by the service for playback. This includes the "foreground service" - * status and notification, the wake locks and possibly the MediaPlayer. - * - * @param releaseMediaPlayer Indicates whether the Media Player should also be released or not - */ - protected void releaseResources(boolean releaseMediaPlayer) { - // stop being a foreground service - stopForeground(true); - - // stop and release the Media Player, if it's available - if (releaseMediaPlayer && mPlayer != null) { - mPlayer.reset(); - mPlayer.release(); - mPlayer = null; - } - - // release the Wifi lock, if holding it - if (mWifiLock.isHeld()) { - mWifiLock.release(); - } - } - - - /** - * Fully releases the audio focus. - */ - private void giveUpAudioFocus() { - if (mAudioFocus == AudioFocus.FOCUS - && mAudioManager != null - && AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.abandonAudioFocus(this)) { - - mAudioFocus = AudioFocus.NO_FOCUS; - } - } - - - /** - * Reconfigures MediaPlayer according to audio focus settings and starts/restarts it. - */ - protected void configAndStartMediaPlayer() { - if (mPlayer == null) { - throw new IllegalStateException("mPlayer is NULL"); - } - - if (mAudioFocus == AudioFocus.NO_FOCUS) { - if (mPlayer.isPlaying()) { - mPlayer.pause(); // have to be polite; but mState is not changed, to resume when focus is received again - } - - } else { - if (mAudioFocus == AudioFocus.NO_FOCUS_CAN_DUCK) { - mPlayer.setVolume(DUCK_VOLUME, DUCK_VOLUME); - - } else { - mPlayer.setVolume(1.0f, 1.0f); // full volume - } - - if (!mPlayer.isPlaying()) { - mPlayer.start(); - } - } - } - - - /** - * Requests the audio focus to the Audio Manager - */ - private void tryToGetAudioFocus() { - if (mAudioFocus != AudioFocus.FOCUS - && mAudioManager != null - && (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.requestAudioFocus( this, - AudioManager.STREAM_MUSIC, - AudioManager.AUDIOFOCUS_GAIN)) - ) { - mAudioFocus = AudioFocus.FOCUS; - } - } - - - /** - * Starts playing the current media file. - */ - protected void playMedia() { - mState = State.STOPPED; - releaseResources(false); // release everything except MediaPlayer - - try { - if (mFile == null) { - Toast.makeText(this, R.string.media_err_nothing_to_play, Toast.LENGTH_LONG).show(); - processStopRequest(true); - return; - - } else if (mAccount == null) { - Toast.makeText(this, R.string.media_err_not_in_owncloud, Toast.LENGTH_LONG).show(); - processStopRequest(true); - return; - } - - createMediaPlayerIfNeeded(); - mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); - String url = mFile.getStoragePath(); - /* Streaming is not possible right now - if (url == null || url.length() <= 0) { - url = AccountUtils.constructFullURLForAccount(this, mAccount) + mFile.getRemotePath(); - } - mIsStreaming = url.startsWith("http:") || url.startsWith("https:"); - */ - mIsStreaming = false; - - mPlayer.setDataSource(url); - - mState = State.PREPARING; - setUpAsForeground(String.format(getString(R.string.media_state_loading), mFile.getFileName())); - - // starts preparing the media player in background - mPlayer.prepareAsync(); - - // prevent the Wifi from going to sleep when streaming - if (mIsStreaming) { - mWifiLock.acquire(); - } else if (mWifiLock.isHeld()) { - mWifiLock.release(); - } - - } catch (SecurityException e) { - Log_OC.e(TAG, "SecurityException playing " + mAccount.name + mFile.getRemotePath(), e); - Toast.makeText(this, String.format(getString(R.string.media_err_security_ex), mFile.getFileName()), Toast.LENGTH_LONG).show(); - processStopRequest(true); - - } catch (IOException e) { - Log_OC.e(TAG, "IOException playing " + mAccount.name + mFile.getRemotePath(), e); - Toast.makeText(this, String.format(getString(R.string.media_err_io_ex), mFile.getFileName()), Toast.LENGTH_LONG).show(); - processStopRequest(true); - - } catch (IllegalStateException e) { - Log_OC.e(TAG, "IllegalStateException " + mAccount.name + mFile.getRemotePath(), e); - Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()), Toast.LENGTH_LONG).show(); - processStopRequest(true); - - } catch (IllegalArgumentException e) { - Log_OC.e(TAG, "IllegalArgumentException " + mAccount.name + mFile.getRemotePath(), e); - Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()), Toast.LENGTH_LONG).show(); - processStopRequest(true); - } - } - - - /** Called when media player is done playing current song. */ - public void onCompletion(MediaPlayer player) { - Toast.makeText(this, String.format(getString(R.string.media_event_done, mFile.getFileName())), Toast.LENGTH_LONG).show(); - if (mMediaController != null) { - // somebody is still bound to the service - player.seekTo(0); - processPauseRequest(); - mMediaController.updatePausePlay(); - } else { - // nobody is bound - processStopRequest(true); - } - return; - } - - - /** - * Called when media player is done preparing. - * - * Time to start. - */ - public void onPrepared(MediaPlayer player) { - mState = State.PLAYING; - updateNotification(String.format(getString(R.string.media_state_playing), mFile.getFileName())); - if (mMediaController != null) { - mMediaController.setEnabled(true); - } - player.seekTo(mStartPosition); - configAndStartMediaPlayer(); - if (!mPlayOnPrepared) { - processPauseRequest(); - } - - if (mMediaController != null) { - mMediaController.updatePausePlay(); - } - } - - - /** - * Updates the status notification - */ - @SuppressWarnings("deprecation") - 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_ACCOUNT, mAccount); - showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), - (int)System.currentTimeMillis(), - showDetailsIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - mNotification.when = System.currentTimeMillis(); - //mNotification.contentView.setTextViewText(R.id.status_text, content); - String ticker = String.format(getString(R.string.media_notif_ticker), getString(R.string.app_name)); - mNotification.setLatestEventInfo(getApplicationContext(), ticker, content, mNotification.contentIntent); - mNotificationManager.notify(R.string.media_notif_ticker, mNotification); - } - - - /** - * Configures the service as a foreground service. - * - * The system will avoid finishing the service as much as possible when resources as low. - * - * A notification must be created to keep the user aware of the existance of the service. - */ - @SuppressWarnings("deprecation") - private void setUpAsForeground(String content) { - /// creates status notification - // TODO put a progress bar to follow the playback progress - mNotification = new Notification(); - mNotification.icon = android.R.drawable.ic_media_play; - //mNotification.tickerText = text; - mNotification.when = System.currentTimeMillis(); - mNotification.flags |= Notification.FLAG_ONGOING_EVENT; - //mNotification.contentView.setTextViewText(R.id.status_text, "ownCloud Music Player"); // NULL POINTER - //mNotification.contentView.setTextViewText(R.id.status_text, getString(R.string.downloader_download_in_progress_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_ACCOUNT, mAccount); - showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), - (int)System.currentTimeMillis(), - showDetailsIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - - - //mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotification); - String ticker = String.format(getString(R.string.media_notif_ticker), getString(R.string.app_name)); - mNotification.setLatestEventInfo(getApplicationContext(), ticker, content, mNotification.contentIntent); - startForeground(R.string.media_notif_ticker, mNotification); - - } - - /** - * Called when there's an error playing media. - * - * Warns the user about the error and resets the media player. - */ - public boolean onError(MediaPlayer mp, int what, int extra) { - Log_OC.e(TAG, "Error in audio playback, what = " + what + ", extra = " + extra); - - String message = getMessageForMediaError(this, what, extra); - Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); - - processStopRequest(true); - return true; - } - - /** - * Called by the system when another app tries to play some sound. - * - * {@inheritDoc} - */ - @Override - public void onAudioFocusChange(int focusChange) { - if (focusChange > 0) { - // focus gain; check AudioManager.AUDIOFOCUS_* values - mAudioFocus = AudioFocus.FOCUS; - // restart media player with new focus settings - if (mState == State.PLAYING) - configAndStartMediaPlayer(); - - } else if (focusChange < 0) { - // focus loss; check AudioManager.AUDIOFOCUS_* values - boolean canDuck = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK == focusChange; - mAudioFocus = canDuck ? AudioFocus.NO_FOCUS_CAN_DUCK : AudioFocus.NO_FOCUS; - // start/restart/pause media player with new focus settings - if (mPlayer != null && mPlayer.isPlaying()) - configAndStartMediaPlayer(); - } - - } - - /** - * Called when the service is finished for final clean-up. - * - * {@inheritDoc} - */ - @Override - public void onDestroy() { - mState = State.STOPPED; - releaseResources(true); - giveUpAudioFocus(); - } - - - /** - * Provides a binder object that clients can use to perform operations on the MediaPlayer managed by the MediaService. - */ - @Override - public IBinder onBind(Intent arg) { - return mBinder; - } - - - /** - * Called when ALL the bound clients were onbound. - * - * The service is destroyed if playback stopped or paused - */ - @Override - public boolean onUnbind(Intent intent) { - if (mState == State.PAUSED || mState == State.STOPPED) { - processStopRequest(false); - } - return false; // not accepting rebinding (default behaviour) - } - - - /** - * Accesses the current MediaPlayer instance in the service. - * - * To be handled carefully. Visibility is protected to be accessed only - * - * @return Current MediaPlayer instance handled by MediaService. - */ - protected MediaPlayer getPlayer() { - return mPlayer; - } - - - /** - * Accesses the current OCFile loaded in the service. - * - * @return The current OCFile loaded in the service. - */ - protected OCFile getCurrentFile() { - return mFile; - } - - - /** - * Accesses the current {@link State} of the MediaService. - * - * @return The current {@link State} of the MediaService. - */ - protected State getState() { - return mState; - } - - - protected void setMediaContoller(MediaControlView mediaController) { - mMediaController = mediaController; - } - - protected MediaControlView getMediaController() { - return mMediaController; - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/media/MediaServiceBinder.java b/src/de/mobilcom/debitel/cloud/android/media/MediaServiceBinder.java deleted file mode 100644 index 935d3e20..00000000 --- a/src/de/mobilcom/debitel/cloud/android/media/MediaServiceBinder.java +++ /dev/null @@ -1,181 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.media; - - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.media.MediaService.State; - -import android.accounts.Account; -import android.content.Intent; -import android.media.MediaPlayer; -import android.os.Binder; -import android.widget.MediaController; - - -/** - * Binder allowing client components to perform operations on on the MediaPlayer managed by a MediaService instance. - * - * Provides the operations of {@link MediaController.MediaPlayerControl}, and an extra method to check if - * an {@link OCFile} instance is handled by the MediaService. - * - * @author David A. Velasco - */ -public class MediaServiceBinder extends Binder implements MediaController.MediaPlayerControl { - - private static final String TAG = MediaServiceBinder.class.getSimpleName(); - /** - * {@link MediaService} instance to access with the binder - */ - private MediaService mService = null; - - /** - * Public constructor - * - * @param service A {@link MediaService} instance to access with the binder - */ - public MediaServiceBinder(MediaService service) { - if (service == null) { - throw new IllegalArgumentException("Argument 'service' can not be null"); - } - mService = service; - } - - - public boolean isPlaying(OCFile mFile) { - return (mFile != null && mFile.equals(mService.getCurrentFile())); - } - - - @Override - public boolean canPause() { - return true; - } - - @Override - public boolean canSeekBackward() { - return true; - } - - @Override - public boolean canSeekForward() { - return true; - } - - @Override - public int getBufferPercentage() { - MediaPlayer currentPlayer = mService.getPlayer(); - if (currentPlayer != null) { - return 100; - // TODO update for streamed playback; add OnBufferUpdateListener in MediaService - } else { - return 0; - } - } - - @Override - public int getCurrentPosition() { - MediaPlayer currentPlayer = mService.getPlayer(); - if (currentPlayer != null) { - int pos = currentPlayer.getCurrentPosition(); - return pos; - } else { - return 0; - } - } - - @Override - public int getDuration() { - MediaPlayer currentPlayer = mService.getPlayer(); - if (currentPlayer != null) { - int dur = currentPlayer.getDuration(); - return dur; - } else { - return 0; - } - } - - - /** - * Reports if the MediaService is playing a file or not. - * - * Considers that the file is being played when it is in preparation because the expected - * client of this method is a {@link MediaController} , and we do not want that the 'play' - * button is shown when the file is being prepared by the MediaService. - */ - @Override - public boolean isPlaying() { - MediaService.State currentState = mService.getState(); - return (currentState == State.PLAYING || (currentState == State.PREPARING && mService.mPlayOnPrepared)); - } - - - @Override - public void pause() { - Log_OC.d(TAG, "Pausing through binder..."); - mService.processPauseRequest(); - } - - @Override - public void seekTo(int pos) { - Log_OC.d(TAG, "Seeking " + pos + " through binder..."); - MediaPlayer currentPlayer = mService.getPlayer(); - MediaService.State currentState = mService.getState(); - if (currentPlayer != null && currentState != State.PREPARING && currentState != State.STOPPED) { - currentPlayer.seekTo(pos); - } - } - - @Override - public void start() { - Log_OC.d(TAG, "Starting through binder..."); - mService.processPlayRequest(); // this will finish the service if there is no file preloaded to play - } - - public void start(Account account, OCFile file, boolean playImmediately, int position) { - 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_PLAY_ON_LOAD, playImmediately); - i.putExtra(MediaService.EXTRA_START_POSITION, position); - i.setAction(MediaService.ACTION_PLAY_FILE); - mService.startService(i); - } - - - public void registerMediaController(MediaControlView mediaController) { - mService.setMediaContoller(mediaController); - } - - public void unregisterMediaController(MediaControlView mediaController) { - if (mediaController != null && mediaController == mService.getMediaController()) { - mService.setMediaContoller(null); - } - - } - - public boolean isInPlaybackState() { - MediaService.State currentState = mService.getState(); - return (currentState == MediaService.State.PLAYING || currentState == MediaService.State.PAUSED); - } - -} - - diff --git a/src/de/mobilcom/debitel/cloud/android/network/AdvancedSslSocketFactory.java b/src/de/mobilcom/debitel/cloud/android/network/AdvancedSslSocketFactory.java deleted file mode 100644 index cf802ceb..00000000 --- a/src/de/mobilcom/debitel/cloud/android/network/AdvancedSslSocketFactory.java +++ /dev/null @@ -1,240 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.network; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.UnknownHostException; -import java.security.cert.X509Certificate; - -import javax.net.SocketFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; - -import org.apache.commons.httpclient.ConnectTimeoutException; -import org.apache.commons.httpclient.params.HttpConnectionParams; -import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; -import org.apache.http.conn.ssl.X509HostnameVerifier; - -import de.mobilcom.debitel.cloud.android.Log_OC; - -/** - * AdvancedSSLProtocolSocketFactory allows to create SSL {@link Socket}s with - * a custom SSLContext and an optional Hostname Verifier. - * - * @author David A. Velasco - */ - -public class AdvancedSslSocketFactory implements ProtocolSocketFactory { - - private static final String TAG = AdvancedSslSocketFactory.class.getSimpleName(); - - private SSLContext mSslContext = null; - private AdvancedX509TrustManager mTrustManager = null; - private X509HostnameVerifier mHostnameVerifier = null; - - public SSLContext getSslContext() { - return mSslContext; - } - - /** - * Constructor for AdvancedSSLProtocolSocketFactory. - */ - public AdvancedSslSocketFactory(SSLContext sslContext, AdvancedX509TrustManager trustManager, X509HostnameVerifier hostnameVerifier) { - if (sslContext == null) - throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null SSLContext"); - if (trustManager == null) - throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null Trust Manager"); - mSslContext = sslContext; - mTrustManager = trustManager; - mHostnameVerifier = hostnameVerifier; - } - - /** - * @see ProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int) - */ - public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException { - Socket socket = mSslContext.getSocketFactory().createSocket(host, port, clientHost, clientPort); - verifyPeerIdentity(host, port, socket); - return socket; - } - - - /** - * Attempts to get a new socket connection to the given host within the - * given time limit. - * - * @param host the host name/IP - * @param port the port on the host - * @param clientHost the local host name/IP to bind the socket to - * @param clientPort the port on the local machine - * @param params {@link HttpConnectionParams Http connection parameters} - * - * @return Socket a new socket - * - * @throws IOException if an I/O error occurs while creating the socket - * @throws UnknownHostException if the IP address of the host cannot be - * determined - */ - public Socket createSocket(final String host, final int port, - final InetAddress localAddress, final int localPort, - final HttpConnectionParams params) throws IOException, - UnknownHostException, ConnectTimeoutException { - Log_OC.d(TAG, "Creating SSL Socket with remote " + host + ":" + port + ", local " + localAddress + ":" + localPort + ", params: " + params); - if (params == null) { - throw new IllegalArgumentException("Parameters may not be null"); - } - int timeout = params.getConnectionTimeout(); - SocketFactory socketfactory = mSslContext.getSocketFactory(); - Log_OC.d(TAG, " ... with connection timeout " + timeout + " and socket timeout " + params.getSoTimeout()); - Socket socket = socketfactory.createSocket(); - SocketAddress localaddr = new InetSocketAddress(localAddress, localPort); - SocketAddress remoteaddr = new InetSocketAddress(host, port); - socket.setSoTimeout(params.getSoTimeout()); - socket.bind(localaddr); - socket.connect(remoteaddr, timeout); - verifyPeerIdentity(host, port, socket); - return socket; - } - - /** - * @see ProtocolSocketFactory#createSocket(java.lang.String,int) - */ - public Socket createSocket(String host, int port) throws IOException, - UnknownHostException { - Log_OC.d(TAG, "Creating SSL Socket with remote " + host + ":" + port); - Socket socket = mSslContext.getSocketFactory().createSocket(host, port); - verifyPeerIdentity(host, port, socket); - return socket; - } - - public boolean equals(Object obj) { - return ((obj != null) && obj.getClass().equals( - AdvancedSslSocketFactory.class)); - } - - public int hashCode() { - return AdvancedSslSocketFactory.class.hashCode(); - } - - - public X509HostnameVerifier getHostNameVerifier() { - return mHostnameVerifier; - } - - - public void setHostNameVerifier(X509HostnameVerifier hostnameVerifier) { - mHostnameVerifier = hostnameVerifier; - } - - /** - * Verifies the identity of the server. - * - * The server certificate is verified first. - * - * Then, the host name is compared with the content of the server certificate using the current host name verifier, if any. - * @param socket - */ - private void verifyPeerIdentity(String host, int port, Socket socket) throws IOException { - try { - CertificateCombinedException failInHandshake = null; - /// 1. VERIFY THE SERVER CERTIFICATE through the registered TrustManager (that should be an instance of AdvancedX509TrustManager) - try { - SSLSocket sock = (SSLSocket) socket; // a new SSLSession instance is created as a "side effect" - sock.startHandshake(); - - } catch (RuntimeException e) { - - if (e instanceof CertificateCombinedException) { - failInHandshake = (CertificateCombinedException) e; - } else { - Throwable cause = e.getCause(); - Throwable previousCause = null; - while (cause != null && cause != previousCause && !(cause instanceof CertificateCombinedException)) { - previousCause = cause; - cause = cause.getCause(); - } - if (cause != null && cause instanceof CertificateCombinedException) { - failInHandshake = (CertificateCombinedException)cause; - } - } - if (failInHandshake == null) { - throw e; - } - failInHandshake.setHostInUrl(host); - - } - - /// 2. VERIFY HOSTNAME - SSLSession newSession = null; - boolean verifiedHostname = true; - if (mHostnameVerifier != null) { - if (failInHandshake != null) { - /// 2.1 : a new SSLSession instance was NOT created in the handshake - X509Certificate serverCert = failInHandshake.getServerCertificate(); - try { - mHostnameVerifier.verify(host, serverCert); - } catch (SSLException e) { - verifiedHostname = false; - } - - } else { - /// 2.2 : a new SSLSession instance was created in the handshake - newSession = ((SSLSocket)socket).getSession(); - if (!mTrustManager.isKnownServer((X509Certificate)(newSession.getPeerCertificates()[0]))) { - verifiedHostname = mHostnameVerifier.verify(host, newSession); - } - } - } - - /// 3. Combine the exceptions to throw, if any - if (!verifiedHostname) { - SSLPeerUnverifiedException pue = new SSLPeerUnverifiedException("Names in the server certificate do not match to " + host + " in the URL"); - if (failInHandshake == null) { - failInHandshake = new CertificateCombinedException((X509Certificate) newSession.getPeerCertificates()[0]); - failInHandshake.setHostInUrl(host); - } - failInHandshake.setSslPeerUnverifiedException(pue); - pue.initCause(failInHandshake); - throw pue; - - } else if (failInHandshake != null) { - SSLHandshakeException hse = new SSLHandshakeException("Server certificate could not be verified"); - hse.initCause(failInHandshake); - throw hse; - } - - } catch (IOException io) { - try { - socket.close(); - } catch (Exception x) { - // NOTHING - irrelevant exception for the caller - } - throw io; - } - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/network/AdvancedX509TrustManager.java b/src/de/mobilcom/debitel/cloud/android/network/AdvancedX509TrustManager.java deleted file mode 100644 index 038ca392..00000000 --- a/src/de/mobilcom/debitel/cloud/android/network/AdvancedX509TrustManager.java +++ /dev/null @@ -1,146 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.network; - -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertPathValidatorException; -import java.security.cert.CertStoreException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateNotYetValidException; -import java.security.cert.X509Certificate; - -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import de.mobilcom.debitel.cloud.android.Log_OC; - -/** - * @author David A. Velasco - */ -public class AdvancedX509TrustManager implements X509TrustManager { - - private static final String TAG = AdvancedX509TrustManager.class.getSimpleName(); - - private X509TrustManager mStandardTrustManager = null; - private KeyStore mKnownServersKeyStore; - - /** - * Constructor for AdvancedX509TrustManager - * - * @param knownServersCertStore Local certificates store with server certificates explicitly trusted by the user. - * @throws CertStoreException When no default X509TrustManager instance was found in the system. - */ - public AdvancedX509TrustManager(KeyStore knownServersKeyStore) - throws NoSuchAlgorithmException, KeyStoreException, CertStoreException { - super(); - TrustManagerFactory factory = TrustManagerFactory - .getInstance(TrustManagerFactory.getDefaultAlgorithm()); - factory.init((KeyStore)null); - mStandardTrustManager = findX509TrustManager(factory); - - mKnownServersKeyStore = knownServersKeyStore; - } - - - /** - * Locates the first X509TrustManager provided by a given TrustManagerFactory - * @param factory TrustManagerFactory to inspect in the search for a X509TrustManager - * @return The first X509TrustManager found in factory. - * @throws CertStoreException When no X509TrustManager instance was found in factory - */ - private X509TrustManager findX509TrustManager(TrustManagerFactory factory) throws CertStoreException { - TrustManager tms[] = factory.getTrustManagers(); - for (int i = 0; i < tms.length; i++) { - if (tms[i] instanceof X509TrustManager) { - return (X509TrustManager) tms[i]; - } - } - return null; - } - - - /** - * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[], - * String authType) - */ - public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException { - mStandardTrustManager.checkClientTrusted(certificates, authType); - } - - - /** - * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], - * String authType) - */ - public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException { - if (!isKnownServer(certificates[0])) { - CertificateCombinedException result = new CertificateCombinedException(certificates[0]); - try { - certificates[0].checkValidity(); - } catch (CertificateExpiredException c) { - result.setCertificateExpiredException(c); - - } catch (CertificateNotYetValidException c) { - result.setCertificateNotYetException(c); - } - - try { - mStandardTrustManager.checkServerTrusted(certificates, authType); - } catch (CertificateException c) { - Throwable cause = c.getCause(); - Throwable previousCause = null; - while (cause != null && cause != previousCause && !(cause instanceof CertPathValidatorException)) { // getCause() is not funny - previousCause = cause; - cause = cause.getCause(); - } - if (cause != null && cause instanceof CertPathValidatorException) { - result.setCertPathValidatorException((CertPathValidatorException)cause); - } else { - result.setOtherCertificateException(c); - } - } - - if (result.isException()) - throw result; - - } - } - - - /** - * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() - */ - public X509Certificate[] getAcceptedIssuers() { - return mStandardTrustManager.getAcceptedIssuers(); - } - - - public boolean isKnownServer(X509Certificate cert) { - try { - return (mKnownServersKeyStore.getCertificateAlias(cert) != null); - } catch (KeyStoreException e) { - Log_OC.d(TAG, "Fail while checking certificate in the known-servers store"); - return false; - } - } - -} \ No newline at end of file diff --git a/src/de/mobilcom/debitel/cloud/android/network/BearerAuthScheme.java b/src/de/mobilcom/debitel/cloud/android/network/BearerAuthScheme.java deleted file mode 100644 index 3d4abc08..00000000 --- a/src/de/mobilcom/debitel/cloud/android/network/BearerAuthScheme.java +++ /dev/null @@ -1,268 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2012 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 de.mobilcom.debitel.cloud.android.network; - -import java.util.Map; - -import org.apache.commons.httpclient.Credentials; -import org.apache.commons.httpclient.HttpMethod; -import org.apache.commons.httpclient.auth.AuthChallengeParser; -import org.apache.commons.httpclient.auth.AuthScheme; -import org.apache.commons.httpclient.auth.AuthenticationException; -import org.apache.commons.httpclient.auth.InvalidCredentialsException; -import org.apache.commons.httpclient.auth.MalformedChallengeException; - -import de.mobilcom.debitel.cloud.android.Log_OC; - -/** - * Bearer authentication scheme as defined in RFC 6750. - * - * @author David A. Velasco - */ - -public class BearerAuthScheme implements AuthScheme /*extends RFC2617Scheme*/ { - - private static final String TAG = BearerAuthScheme.class.getSimpleName(); - - public static final String AUTH_POLICY = "Bearer"; - - /** Whether the bearer authentication process is complete */ - private boolean mComplete; - - /** Authentication parameter map */ - private Map mParams = null; - - - /** - * Default constructor for the bearer authentication scheme. - */ - public BearerAuthScheme() { - mComplete = false; - } - - /** - * Constructor for the basic authentication scheme. - * - * @param challenge Authentication challenge - * - * @throws MalformedChallengeException Thrown if the authentication challenge is malformed - * - * @deprecated Use parameterless constructor and {@link AuthScheme#processChallenge(String)} method - */ - public BearerAuthScheme(final String challenge) throws MalformedChallengeException { - processChallenge(challenge); - mComplete = true; - } - - /** - * Returns textual designation of the bearer authentication scheme. - * - * @return "Bearer" - */ - public String getSchemeName() { - return "bearer"; - } - - /** - * Processes the Bearer challenge. - * - * @param challenge The challenge string - * - * @throws MalformedChallengeException Thrown if the authentication challenge is malformed - */ - public void processChallenge(String challenge) throws MalformedChallengeException { - String s = AuthChallengeParser.extractScheme(challenge); - if (!s.equalsIgnoreCase(getSchemeName())) { - throw new MalformedChallengeException( - "Invalid " + getSchemeName() + " challenge: " + challenge); - } - mParams = AuthChallengeParser.extractParams(challenge); - mComplete = true; - } - - /** - * Tests if the Bearer authentication process has been completed. - * - * @return 'true' if Bearer authorization has been processed, 'false' otherwise. - */ - public boolean isComplete() { - return this.mComplete; - } - - /** - * Produces bearer authorization string for the given set of - * {@link Credentials}. - * - * @param credentials The set of credentials to be used for authentication - * @param method Method name is ignored by the bearer authentication scheme - * @param uri URI is ignored by the bearer authentication scheme - * @throws InvalidCredentialsException If authentication credentials are not valid or not applicable - * for this authentication scheme - * @throws AuthenticationException If authorization string cannot be generated due to an authentication failure - * @return A bearer authorization string - * - * @deprecated Use {@link #authenticate(Credentials, HttpMethod)} - */ - public String authenticate(Credentials credentials, String method, String uri) throws AuthenticationException { - Log_OC.d(TAG, "enter BearerScheme.authenticate(Credentials, String, String)"); - - BearerCredentials bearer = null; - try { - bearer = (BearerCredentials) credentials; - } catch (ClassCastException e) { - throw new InvalidCredentialsException( - "Credentials cannot be used for bearer authentication: " - + credentials.getClass().getName()); - } - return BearerAuthScheme.authenticate(bearer); - } - - - /** - * Returns 'false'. Bearer authentication scheme is request based. - * - * @return 'false'. - */ - public boolean isConnectionBased() { - return false; - } - - /** - * Produces bearer authorization string for the given set of {@link Credentials}. - * - * @param credentials The set of credentials to be used for authentication - * @param method The method being authenticated - * @throws InvalidCredentialsException If authentication credentials are not valid or not applicable for this authentication - * scheme. - * @throws AuthenticationException If authorization string cannot be generated due to an authentication failure. - * - * @return a basic authorization string - */ - public String authenticate(Credentials credentials, HttpMethod method) throws AuthenticationException { - Log_OC.d(TAG, "enter BearerScheme.authenticate(Credentials, HttpMethod)"); - - if (method == null) { - throw new IllegalArgumentException("Method may not be null"); - } - BearerCredentials bearer = null; - try { - bearer = (BearerCredentials) credentials; - } catch (ClassCastException e) { - throw new InvalidCredentialsException( - "Credentials cannot be used for bearer authentication: " - + credentials.getClass().getName()); - } - return BearerAuthScheme.authenticate( - bearer, - method.getParams().getCredentialCharset()); - } - - /** - * @deprecated Use {@link #authenticate(BearerCredentials, String)} - * - * Returns a bearer Authorization header value for the given - * {@link BearerCredentials}. - * - * @param credentials The credentials to encode. - * - * @return A bearer authorization string - */ - public static String authenticate(BearerCredentials credentials) { - return authenticate(credentials, "ISO-8859-1"); - } - - /** - * Returns a bearer Authorization header value for the given - * {@link BearerCredentials} and charset. - * - * @param credentials The credentials to encode. - * @param charset The charset to use for encoding the credentials - * - * @return A bearer authorization string - * - * @since 3.0 - */ - public static String authenticate(BearerCredentials credentials, String charset) { - Log_OC.d(TAG, "enter BearerAuthScheme.authenticate(BearerCredentials, String)"); - - if (credentials == null) { - throw new IllegalArgumentException("Credentials may not be null"); - } - if (charset == null || charset.length() == 0) { - throw new IllegalArgumentException("charset may not be null or empty"); - } - StringBuffer buffer = new StringBuffer(); - buffer.append(credentials.getAccessToken()); - - //return "Bearer " + EncodingUtil.getAsciiString(EncodingUtil.getBytes(buffer.toString(), charset)); - return "Bearer " + buffer.toString(); - } - - /** - * Returns a String identifying the authentication challenge. This is - * used, in combination with the host and port to determine if - * authorization has already been attempted or not. Schemes which - * require multiple requests to complete the authentication should - * return a different value for each stage in the request. - * - * Additionally, the ID should take into account any changes to the - * authentication challenge and return a different value when appropriate. - * For example when the realm changes in basic authentication it should be - * considered a different authentication attempt and a different value should - * be returned. - * - * This method simply returns the realm for the challenge. - * - * @return String a String identifying the authentication challenge. - * - * @deprecated no longer used - */ - @Override - public String getID() { - return getRealm(); - } - - /** - * Returns authentication parameter with the given name, if available. - * - * @param name The name of the parameter to be returned - * - * @return The parameter with the given name - */ - @Override - public String getParameter(String name) { - if (name == null) { - throw new IllegalArgumentException("Parameter name may not be null"); - } - if (mParams == null) { - return null; - } - return (String) mParams.get(name.toLowerCase()); - } - - /** - * Returns authentication realm. The realm may not be null. - * - * @return The authentication realm - */ - @Override - public String getRealm() { - return getParameter("realm"); - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/network/BearerCredentials.java b/src/de/mobilcom/debitel/cloud/android/network/BearerCredentials.java deleted file mode 100644 index 12e62e9c..00000000 --- a/src/de/mobilcom/debitel/cloud/android/network/BearerCredentials.java +++ /dev/null @@ -1,97 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2012 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 de.mobilcom.debitel.cloud.android.network; - -import org.apache.commons.httpclient.Credentials; -import org.apache.commons.httpclient.util.LangUtils; - -/** - * Bearer token {@link Credentials} - * - * @author David A. Velasco - */ -public class BearerCredentials implements Credentials { - - - private String mAccessToken; - - - /** - * The constructor with the bearer token - * - * @param token The bearer token - */ - public BearerCredentials(String token) { - /*if (token == null) { - throw new IllegalArgumentException("Bearer token may not be null"); - }*/ - mAccessToken = (token == null) ? "" : token; - } - - - /** - * Returns the access token - * - * @return The access token - */ - public String getAccessToken() { - return mAccessToken; - } - - - /** - * Get this object string. - * - * @return The access token - */ - public String toString() { - return mAccessToken; - } - - /** - * Does a hash of the access token. - * - * @return The hash code of the access token - */ - public int hashCode() { - int hash = LangUtils.HASH_SEED; - hash = LangUtils.hashCode(hash, mAccessToken); - return hash; - } - - /** - * These credentials are assumed equal if accessToken is the same. - * - * @param o The other object to compare with. - * - * @return 'True' if the object is equivalent. - */ - public boolean equals(Object o) { - if (o == null) return false; - if (this == o) return true; - if (this.getClass().equals(o.getClass())) { - BearerCredentials that = (BearerCredentials) o; - if (LangUtils.equals(mAccessToken, that.mAccessToken)) { - return true; - } - } - return false; - } - -} - diff --git a/src/de/mobilcom/debitel/cloud/android/network/CertificateCombinedException.java b/src/de/mobilcom/debitel/cloud/android/network/CertificateCombinedException.java deleted file mode 100644 index d767ea07..00000000 --- a/src/de/mobilcom/debitel/cloud/android/network/CertificateCombinedException.java +++ /dev/null @@ -1,130 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.network; - -import java.security.cert.CertPathValidatorException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateNotYetValidException; -import java.security.cert.X509Certificate; - -import javax.net.ssl.SSLPeerUnverifiedException; - -/** - * Exception joining all the problems that {@link AdvancedX509TrustManager} can find in - * a certificate chain for a server. - * - * This was initially created as an extension of CertificateException, but some - * implementations of the SSL socket layer in existing devices are REPLACING the CertificateException - * instances thrown by {@link javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], String)} - * with SSLPeerUnverifiedException FORGETTING THE CAUSING EXCEPTION instead of wrapping it. - * - * Due to this, extending RuntimeException is necessary to get that the CertificateCombinedException - * instance reaches {@link AdvancedSslSocketFactory#verifyPeerIdentity}. - * - * BE CAREFUL. As a RuntimeException extensions, Java compilers do not require to handle it - * in client methods. Be sure to use it only when you know exactly where it will go. - * - * @author David A. Velasco - */ -public class CertificateCombinedException extends RuntimeException { - - /** Generated - to refresh every time the class changes */ - private static final long serialVersionUID = -8875782030758554999L; - - private X509Certificate mServerCert = null; - private String mHostInUrl; - - private CertificateExpiredException mCertificateExpiredException = null; - private CertificateNotYetValidException mCertificateNotYetValidException = null; - private CertPathValidatorException mCertPathValidatorException = null; - private CertificateException mOtherCertificateException = null; - private SSLPeerUnverifiedException mSslPeerUnverifiedException = null; - - public CertificateCombinedException(X509Certificate x509Certificate) { - mServerCert = x509Certificate; - } - - public X509Certificate getServerCertificate() { - return mServerCert; - } - - public String getHostInUrl() { - return mHostInUrl; - } - - public void setHostInUrl(String host) { - mHostInUrl = host; - } - - public CertificateExpiredException getCertificateExpiredException() { - return mCertificateExpiredException; - } - - public void setCertificateExpiredException(CertificateExpiredException c) { - mCertificateExpiredException = c; - } - - public CertificateNotYetValidException getCertificateNotYetValidException() { - return mCertificateNotYetValidException; - } - - public void setCertificateNotYetException(CertificateNotYetValidException c) { - mCertificateNotYetValidException = c; - } - - public CertPathValidatorException getCertPathValidatorException() { - return mCertPathValidatorException; - } - - public void setCertPathValidatorException(CertPathValidatorException c) { - mCertPathValidatorException = c; - } - - public CertificateException getOtherCertificateException() { - return mOtherCertificateException; - } - - public void setOtherCertificateException(CertificateException c) { - mOtherCertificateException = c; - } - - public SSLPeerUnverifiedException getSslPeerUnverifiedException() { - return mSslPeerUnverifiedException ; - } - - public void setSslPeerUnverifiedException(SSLPeerUnverifiedException s) { - mSslPeerUnverifiedException = s; - } - - public boolean isException() { - return (mCertificateExpiredException != null || - mCertificateNotYetValidException != null || - mCertPathValidatorException != null || - mOtherCertificateException != null || - mSslPeerUnverifiedException != null); - } - - public boolean isRecoverable() { - return (mCertificateExpiredException != null || - mCertificateNotYetValidException != null || - mCertPathValidatorException != null || - mSslPeerUnverifiedException != null); - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/network/OwnCloudClientUtils.java b/src/de/mobilcom/debitel/cloud/android/network/OwnCloudClientUtils.java deleted file mode 100644 index 767d9bb2..00000000 --- a/src/de/mobilcom/debitel/cloud/android/network/OwnCloudClientUtils.java +++ /dev/null @@ -1,284 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.network; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; - -import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; -import org.apache.commons.httpclient.protocol.Protocol; -import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier; -import org.apache.http.conn.ssl.X509HostnameVerifier; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.authentication.AccountAuthenticator; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils.AccountNotFoundException; - -import eu.alefzero.webdav.WebdavClient; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AccountManagerFuture; -import android.accounts.AuthenticatorException; -import android.accounts.OperationCanceledException; -import android.app.Activity; -import android.content.Context; -import android.net.Uri; -import android.os.Bundle; - -public class OwnCloudClientUtils { - - final private static String TAG = OwnCloudClientUtils.class.getSimpleName(); - - /** Default timeout for waiting data from the server */ - public static final int DEFAULT_DATA_TIMEOUT = 60000; - - /** Default timeout for establishing a connection */ - public static final int DEFAULT_CONNECTION_TIMEOUT = 60000; - - /** Connection manager for all the WebdavClients */ - private static MultiThreadedHttpConnectionManager mConnManager = null; - - private static Protocol mDefaultHttpsProtocol = null; - - private static AdvancedSslSocketFactory mAdvancedSslSocketFactory = null; - - private static X509HostnameVerifier mHostnameVerifier = null; - - - /** - * Creates a WebdavClient setup for an ownCloud account - * - * Do not call this method from the main thread. - * - * @param account The ownCloud account - * @param appContext Android application context - * @return A WebdavClient object ready to be used - * @throws AuthenticatorException If the authenticator failed to get the authorization token for the account. - * @throws OperationCanceledException If the authenticator operation was cancelled while getting the authorization token for the account. - * @throws IOException If there was some I/O error while getting the authorization token for the account. - * @throws AccountNotFoundException If 'account' is unknown for the AccountManager - */ - public static WebdavClient createOwnCloudClient (Account account, Context appContext) throws OperationCanceledException, AuthenticatorException, IOException, AccountNotFoundException { - //Log_OC.d(TAG, "Creating WebdavClient associated to " + account.name); - - Uri uri = Uri.parse(AccountUtils.constructFullURLForAccount(appContext, account)); - AccountManager am = AccountManager.get(appContext); - boolean isOauth2 = am.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null; // TODO avoid calling to getUserData here - boolean isSamlSso = am.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_SAML_WEB_SSO) != null; - WebdavClient client = createOwnCloudClient(uri, appContext, !isSamlSso); - if (isOauth2) { - String accessToken = am.blockingGetAuthToken(account, MainApp.getAuthTokenTypeAccessToken(), false); - client.setBearerCredentials(accessToken); // TODO not assume that the access token is a bearer token - - } else if (isSamlSso) { // TODO avoid a call to getUserData here - String accessToken = am.blockingGetAuthToken(account, MainApp.getAuthTokenTypeSamlSessionCookie(), false); - client.setSsoSessionCookie(accessToken); - - } else { - String username = account.name.substring(0, account.name.lastIndexOf('@')); - //String password = am.getPassword(account); - String password = am.blockingGetAuthToken(account, MainApp.getAuthTokenTypePass(), false); - client.setBasicCredentials(username, password); - } - - return client; - } - - - public static WebdavClient createOwnCloudClient (Account account, Context appContext, Activity currentActivity) throws OperationCanceledException, AuthenticatorException, IOException, AccountNotFoundException { - Uri uri = Uri.parse(AccountUtils.constructFullURLForAccount(appContext, account)); - AccountManager am = AccountManager.get(appContext); - boolean isOauth2 = am.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null; // TODO avoid calling to getUserData here - boolean isSamlSso = am.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_SAML_WEB_SSO) != null; - WebdavClient client = createOwnCloudClient(uri, appContext, !isSamlSso); - - if (isOauth2) { // TODO avoid a call to getUserData here - AccountManagerFuture future = am.getAuthToken(account, MainApp.getAuthTokenTypeAccessToken(), null, currentActivity, null, null); - Bundle result = future.getResult(); - String accessToken = result.getString(AccountManager.KEY_AUTHTOKEN); - if (accessToken == null) throw new AuthenticatorException("WTF!"); - client.setBearerCredentials(accessToken); // TODO not assume that the access token is a bearer token - - } else if (isSamlSso) { // TODO avoid a call to getUserData here - AccountManagerFuture future = am.getAuthToken(account, MainApp.getAuthTokenTypeSamlSessionCookie(), null, currentActivity, null, null); - Bundle result = future.getResult(); - String accessToken = result.getString(AccountManager.KEY_AUTHTOKEN); - if (accessToken == null) throw new AuthenticatorException("WTF!"); - client.setSsoSessionCookie(accessToken); - - } else { - String username = account.name.substring(0, account.name.lastIndexOf('@')); - //String password = am.getPassword(account); - //String password = am.blockingGetAuthToken(account, MainApp.getAuthTokenTypePass(), false); - AccountManagerFuture future = am.getAuthToken(account, MainApp.getAuthTokenTypePass(), null, currentActivity, null, null); - Bundle result = future.getResult(); - String password = result.getString(AccountManager.KEY_AUTHTOKEN); - client.setBasicCredentials(username, password); - } - - return client; - } - - /** - * Creates a WebdavClient to access a URL and sets the desired parameters for ownCloud client connections. - * - * @param uri URL to the ownCloud server - * @param context Android context where the WebdavClient is being created. - * @return A WebdavClient object ready to be used - */ - public static WebdavClient createOwnCloudClient(Uri uri, Context context, boolean followRedirects) { - try { - registerAdvancedSslContext(true, context); - } catch (GeneralSecurityException e) { - Log_OC.e(TAG, "Advanced SSL Context could not be loaded. Default SSL management in the system will be used for HTTPS connections", e); - - } catch (IOException e) { - Log_OC.e(TAG, "The local server truststore could not be read. Default SSL management in the system will be used for HTTPS connections", e); - } - - WebdavClient client = new WebdavClient(getMultiThreadedConnManager()); - - client.setDefaultTimeouts(DEFAULT_DATA_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT); - client.setBaseUri(uri); - client.setFollowRedirects(followRedirects); - - return client; - } - - - /** - * Registers or unregisters the proper components for advanced SSL handling. - * @throws IOException - */ - private static void registerAdvancedSslContext(boolean register, Context context) throws GeneralSecurityException, IOException { - Protocol pr = null; - try { - pr = Protocol.getProtocol("https"); - if (pr != null && mDefaultHttpsProtocol == null) { - mDefaultHttpsProtocol = pr; - } - } catch (IllegalStateException e) { - // nothing to do here; really - } - boolean isRegistered = (pr != null && pr.getSocketFactory() instanceof AdvancedSslSocketFactory); - if (register && !isRegistered) { - Protocol.registerProtocol("https", new Protocol("https", getAdvancedSslSocketFactory(context), 443)); - - } else if (!register && isRegistered) { - if (mDefaultHttpsProtocol != null) { - Protocol.registerProtocol("https", mDefaultHttpsProtocol); - } - } - } - - public static AdvancedSslSocketFactory getAdvancedSslSocketFactory(Context context) throws GeneralSecurityException, IOException { - if (mAdvancedSslSocketFactory == null) { - KeyStore trustStore = getKnownServersStore(context); - AdvancedX509TrustManager trustMgr = new AdvancedX509TrustManager(trustStore); - TrustManager[] tms = new TrustManager[] { trustMgr }; - - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, tms, null); - - mHostnameVerifier = new BrowserCompatHostnameVerifier(); - mAdvancedSslSocketFactory = new AdvancedSslSocketFactory(sslContext, trustMgr, mHostnameVerifier); - } - return mAdvancedSslSocketFactory; - } - - - private static String LOCAL_TRUSTSTORE_FILENAME = "knownServers.bks"; - - private static String LOCAL_TRUSTSTORE_PASSWORD = "password"; - - private static KeyStore mKnownServersStore = null; - - /** - * Returns the local store of reliable server certificates, explicitly accepted by the user. - * - * Returns a KeyStore instance with empty content if the local store was never created. - * - * Loads the store from the storage environment if needed. - * - * @param context Android context where the operation is being performed. - * @return KeyStore instance with explicitly-accepted server certificates. - * @throws KeyStoreException When the KeyStore instance could not be created. - * @throws IOException When an existing local trust store could not be loaded. - * @throws NoSuchAlgorithmException When the existing local trust store was saved with an unsupported algorithm. - * @throws CertificateException When an exception occurred while loading the certificates from the local trust store. - */ - private static KeyStore getKnownServersStore(Context context) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { - if (mKnownServersStore == null) { - //mKnownServersStore = KeyStore.getInstance("BKS"); - mKnownServersStore = KeyStore.getInstance(KeyStore.getDefaultType()); - File localTrustStoreFile = new File(context.getFilesDir(), LOCAL_TRUSTSTORE_FILENAME); - Log_OC.d(TAG, "Searching known-servers store at " + localTrustStoreFile.getAbsolutePath()); - if (localTrustStoreFile.exists()) { - InputStream in = new FileInputStream(localTrustStoreFile); - try { - mKnownServersStore.load(in, LOCAL_TRUSTSTORE_PASSWORD.toCharArray()); - } finally { - in.close(); - } - } else { - mKnownServersStore.load(null, LOCAL_TRUSTSTORE_PASSWORD.toCharArray()); // necessary to initialize an empty KeyStore instance - } - } - return mKnownServersStore; - } - - - public static void addCertToKnownServersStore(Certificate cert, Context context) throws KeyStoreException, NoSuchAlgorithmException, - CertificateException, IOException { - KeyStore knownServers = getKnownServersStore(context); - knownServers.setCertificateEntry(Integer.toString(cert.hashCode()), cert); - FileOutputStream fos = null; - try { - fos = context.openFileOutput(LOCAL_TRUSTSTORE_FILENAME, Context.MODE_PRIVATE); - knownServers.store(fos, LOCAL_TRUSTSTORE_PASSWORD.toCharArray()); - } finally { - fos.close(); - } - } - - - static private MultiThreadedHttpConnectionManager getMultiThreadedConnManager() { - if (mConnManager == null) { - mConnManager = new MultiThreadedHttpConnectionManager(); - mConnManager.getParams().setDefaultMaxConnectionsPerHost(5); - mConnManager.getParams().setMaxTotalConnections(5); - } - return mConnManager; - } - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/network/ProgressiveDataTransferer.java b/src/de/mobilcom/debitel/cloud/android/network/ProgressiveDataTransferer.java deleted file mode 100644 index f066c8a9..00000000 --- a/src/de/mobilcom/debitel/cloud/android/network/ProgressiveDataTransferer.java +++ /dev/null @@ -1,32 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.network; - -import java.util.Collection; - -import eu.alefzero.webdav.OnDatatransferProgressListener; - -public interface ProgressiveDataTransferer { - - public void addDatatransferProgressListener (OnDatatransferProgressListener listener); - - public void addDatatransferProgressListeners(Collection listeners); - - public void removeDatatransferProgressListener(OnDatatransferProgressListener listener); - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/ChunkedUploadFileOperation.java b/src/de/mobilcom/debitel/cloud/android/operations/ChunkedUploadFileOperation.java deleted file mode 100644 index 3c307246..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/ChunkedUploadFileOperation.java +++ /dev/null @@ -1,96 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.operations; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; -import java.util.Random; - -import org.apache.commons.httpclient.HttpException; -import org.apache.commons.httpclient.methods.PutMethod; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.network.ProgressiveDataTransferer; - -import android.accounts.Account; - -import eu.alefzero.webdav.ChunkFromFileChannelRequestEntity; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; - -public class ChunkedUploadFileOperation extends UploadFileOperation { - - private static final long CHUNK_SIZE = 1024000; - private static final String OC_CHUNKED_HEADER = "OC-Chunked"; - private static final String TAG = ChunkedUploadFileOperation.class.getSimpleName(); - - public ChunkedUploadFileOperation( Account account, - OCFile file, - boolean isInstant, - boolean forceOverwrite, - int localBehaviour) { - - super(account, file, isInstant, forceOverwrite, localBehaviour); - } - - @Override - protected int uploadFile(WebdavClient client) throws HttpException, IOException { - int status = -1; - - FileChannel channel = null; - RandomAccessFile raf = null; - try { - File file = new File(getStoragePath()); - raf = new RandomAccessFile(file, "r"); - channel = raf.getChannel(); - mEntity = new ChunkFromFileChannelRequestEntity(channel, getMimeType(), CHUNK_SIZE, file); - ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListeners(getDataTransferListeners()); - long offset = 0; - String uriPrefix = client.getBaseUri() + WebdavUtils.encodePath(getRemotePath()) + "-chunking-" + Math.abs((new Random()).nextInt(9000)+1000) + "-" ; - long chunkCount = (long) Math.ceil((double)file.length() / CHUNK_SIZE); - for (int chunkIndex = 0; chunkIndex < chunkCount ; chunkIndex++, offset += CHUNK_SIZE) { - if (mPutMethod != null) { - mPutMethod.releaseConnection(); // let the connection available for other methods - } - mPutMethod = new PutMethod(uriPrefix + chunkCount + "-" + chunkIndex); - mPutMethod.addRequestHeader(OC_CHUNKED_HEADER, OC_CHUNKED_HEADER); - ((ChunkFromFileChannelRequestEntity)mEntity).setOffset(offset); - mPutMethod.setRequestEntity(mEntity); - status = client.executeMethod(mPutMethod); - client.exhaustResponse(mPutMethod.getResponseBodyAsStream()); - Log_OC.d(TAG, "Upload of " + getStoragePath() + " to " + getRemotePath() + ", chunk index " + chunkIndex + ", count " + chunkCount + ", HTTP result status " + status); - if (!isSuccess(status)) - break; - } - - } finally { - if (channel != null) - channel.close(); - if (raf != null) - raf.close(); - if (mPutMethod != null) - mPutMethod.releaseConnection(); // let the connection available for other methods - } - return status; - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/CreateFolderOperation.java b/src/de/mobilcom/debitel/cloud/android/operations/CreateFolderOperation.java deleted file mode 100644 index f9bc65ce..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/CreateFolderOperation.java +++ /dev/null @@ -1,117 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2012 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 de.mobilcom.debitel.cloud.android.operations; - -import java.io.File; - -import org.apache.commons.httpclient.HttpStatus; -import org.apache.jackrabbit.webdav.client.methods.MkColMethod; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.datamodel.DataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; - -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; - -/** - * Remote operation performing the creation of a new folder in the ownCloud server. - * - * @author David A. Velasco - */ -public class CreateFolderOperation extends RemoteOperation { - - private static final String TAG = CreateFolderOperation.class.getSimpleName(); - - private static final int READ_TIMEOUT = 10000; - private static final int CONNECTION_TIMEOUT = 5000; - - protected String mRemotePath; - protected boolean mCreateFullPath; - protected DataStorageManager mStorageManager; - - /** - * Constructor - * - * @param remotePath Full path to the new directory to create in the remote server. - * @param createFullPath 'True' means that all the ancestor folders should be created if don't exist yet. - * @param storageManager Reference to the local database corresponding to the account where the file is contained. - */ - public CreateFolderOperation(String remotePath, boolean createFullPath, DataStorageManager storageManager) { - mRemotePath = remotePath; - mCreateFullPath = createFullPath; - mStorageManager = storageManager; - } - - - /** - * Performs the operation - * - * @param client Client object to communicate with the remote ownCloud server. - */ - @Override - protected RemoteOperationResult run(WebdavClient client) { - RemoteOperationResult result = null; - MkColMethod mkcol = null; - try { - mkcol = new MkColMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath)); - int status = client.executeMethod(mkcol, READ_TIMEOUT, CONNECTION_TIMEOUT); - if (!mkcol.succeeded() && mkcol.getStatusCode() == HttpStatus.SC_CONFLICT && mCreateFullPath) { - result = createParentFolder(getParentPath(), client); - status = client.executeMethod(mkcol, READ_TIMEOUT, CONNECTION_TIMEOUT); // second (and last) try - } - if (mkcol.succeeded()) { - // Save new directory in local database - OCFile newDir = new OCFile(mRemotePath); - newDir.setMimetype("DIR"); - long parentId = mStorageManager.getFileByPath(getParentPath()).getFileId(); - newDir.setParentId(parentId); - newDir.setModificationTimestamp(System.currentTimeMillis()); - mStorageManager.saveFile(newDir); - } - result = new RemoteOperationResult(mkcol.succeeded(), status, mkcol.getResponseHeaders()); - Log_OC.d(TAG, "Create directory " + mRemotePath + ": " + result.getLogMessage()); - client.exhaustResponse(mkcol.getResponseBodyAsStream()); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Create directory " + mRemotePath + ": " + result.getLogMessage(), e); - - } finally { - if (mkcol != null) - mkcol.releaseConnection(); - } - return result; - } - - - private String getParentPath() { - String parentPath = new File(mRemotePath).getParent(); - parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR; - return parentPath; - } - - - private RemoteOperationResult createParentFolder(String parentPath, WebdavClient client) { - RemoteOperation operation = new CreateFolderOperation( parentPath, - mCreateFullPath, - mStorageManager ); - return operation.execute(client); - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/DownloadFileOperation.java b/src/de/mobilcom/debitel/cloud/android/operations/DownloadFileOperation.java deleted file mode 100644 index 7fcb52f9..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/DownloadFileOperation.java +++ /dev/null @@ -1,235 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.operations; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.apache.commons.httpclient.Header; -import org.apache.commons.httpclient.HttpException; -import org.apache.commons.httpclient.methods.GetMethod; -import org.apache.http.HttpStatus; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult; -import de.mobilcom.debitel.cloud.android.utils.FileStorageUtils; - -import eu.alefzero.webdav.OnDatatransferProgressListener; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; -import android.accounts.Account; -import android.webkit.MimeTypeMap; - -/** - * Remote operation performing the download of a file to an ownCloud server - * - * @author David A. Velasco - */ -public class DownloadFileOperation extends RemoteOperation { - - private static final String TAG = DownloadFileOperation.class.getSimpleName(); - - private Account mAccount; - private OCFile mFile; - private Set mDataTransferListeners = new HashSet(); - private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false); - private long mModificationTimestamp = 0; - private GetMethod mGet; - - - public DownloadFileOperation(Account account, OCFile file) { - if (account == null) - throw new IllegalArgumentException("Illegal null account in DownloadFileOperation creation"); - if (file == null) - throw new IllegalArgumentException("Illegal null file in DownloadFileOperation creation"); - - mAccount = account; - mFile = file; - } - - - public Account getAccount() { - return mAccount; - } - - public OCFile getFile() { - return mFile; - } - - public String getSavePath() { - String path = mFile.getStoragePath(); // re-downloads should be done over the original file - if (path != null && path.length() > 0) { - return path; - } - return FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); - } - - public String getTmpPath() { - return FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath(); - } - - public String getRemotePath() { - return mFile.getRemotePath(); - } - - public String getMimeType() { - String mimeType = mFile.getMimetype(); - if (mimeType == null || mimeType.length() <= 0) { - try { - mimeType = MimeTypeMap.getSingleton() - .getMimeTypeFromExtension( - mFile.getRemotePath().substring(mFile.getRemotePath().lastIndexOf('.') + 1)); - } catch (IndexOutOfBoundsException e) { - Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + mFile.getRemotePath()); - } - } - if (mimeType == null) { - mimeType = "application/octet-stream"; - } - return mimeType; - } - - public long getSize() { - return mFile.getFileLength(); - } - - public long getModificationTimestamp() { - return (mModificationTimestamp > 0) ? mModificationTimestamp : mFile.getModificationTimestamp(); - } - - - public void addDatatransferProgressListener (OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.add(listener); - } - } - - public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.remove(listener); - } - } - - @Override - protected RemoteOperationResult run(WebdavClient client) { - RemoteOperationResult result = null; - File newFile = null; - boolean moved = true; - - /// download will be performed to a temporal file, then moved to the final location - File tmpFile = new File(getTmpPath()); - - /// perform the download - try { - tmpFile.getParentFile().mkdirs(); - int status = downloadFile(client, tmpFile); - if (isSuccess(status)) { - newFile = new File(getSavePath()); - newFile.getParentFile().mkdirs(); - moved = tmpFile.renameTo(newFile); - } - if (!moved) - result = new RemoteOperationResult(RemoteOperationResult.ResultCode.LOCAL_STORAGE_NOT_MOVED); - else - result = new RemoteOperationResult(isSuccess(status), status, (mGet != null ? mGet.getResponseHeaders() : null)); - Log_OC.i(TAG, "Download of " + mFile.getRemotePath() + " to " + getSavePath() + ": " + result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Download of " + mFile.getRemotePath() + " to " + getSavePath() + ": " + result.getLogMessage(), e); - } - - return result; - } - - - public boolean isSuccess(int status) { - return (status == HttpStatus.SC_OK); - } - - - protected int downloadFile(WebdavClient client, File targetFile) throws HttpException, IOException, OperationCancelledException { - int status = -1; - boolean savedFile = false; - mGet = new GetMethod(client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath())); - Iterator it = null; - - FileOutputStream fos = null; - try { - status = client.executeMethod(mGet); - if (isSuccess(status)) { - targetFile.createNewFile(); - BufferedInputStream bis = new BufferedInputStream(mGet.getResponseBodyAsStream()); - fos = new FileOutputStream(targetFile); - long transferred = 0; - - byte[] bytes = new byte[4096]; - int readResult = 0; - while ((readResult = bis.read(bytes)) != -1) { - synchronized(mCancellationRequested) { - if (mCancellationRequested.get()) { - mGet.abort(); - throw new OperationCancelledException(); - } - } - fos.write(bytes, 0, readResult); - transferred += readResult; - synchronized (mDataTransferListeners) { - it = mDataTransferListeners.iterator(); - while (it.hasNext()) { - it.next().onTransferProgress(readResult, transferred, mFile.getFileLength(), targetFile.getName()); - } - } - } - savedFile = true; - Header modificationTime = mGet.getResponseHeader("Last-Modified"); - if (modificationTime != null) { - Date d = WebdavUtils.parseResponseDate((String) modificationTime.getValue()); - mModificationTimestamp = (d != null) ? d.getTime() : 0; - } - - } else { - client.exhaustResponse(mGet.getResponseBodyAsStream()); - } - - } finally { - if (fos != null) fos.close(); - if (!savedFile && targetFile.exists()) { - targetFile.delete(); - } - mGet.releaseConnection(); // let the connection available for other methods - } - return status; - } - - - public void cancel() { - mCancellationRequested.set(true); // atomic set; there is no need of synchronizing it - } - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/ExistenceCheckOperation.java b/src/de/mobilcom/debitel/cloud/android/operations/ExistenceCheckOperation.java deleted file mode 100644 index b7088b03..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/ExistenceCheckOperation.java +++ /dev/null @@ -1,95 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2012 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 de.mobilcom.debitel.cloud.android.operations; - -import org.apache.commons.httpclient.HttpStatus; -import org.apache.commons.httpclient.methods.HeadMethod; - -import de.mobilcom.debitel.cloud.android.Log_OC; - -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; -import android.content.Context; -import android.net.ConnectivityManager; - -/** - * Operation to check the existence or absence of a path in a remote server. - * - * @author David A. Velasco - */ -public class ExistenceCheckOperation extends RemoteOperation { - - /** Maximum time to wait for a response from the server in MILLISECONDs. */ - public static final int TIMEOUT = 10000; - - private static final String TAG = ExistenceCheckOperation.class.getSimpleName(); - - private String mPath; - private Context mContext; - private boolean mSuccessIfAbsent; - - - /** - * Full constructor. Success of the operation will depend upon the value of successIfAbsent. - * - * @param path Path to append to the URL owned by the client instance. - * @param context Android application context. - * @param successIfAbsent When 'true', the operation finishes in success if the path does NOT exist in the remote server (HTTP 404). - */ - public ExistenceCheckOperation(String path, Context context, boolean successIfAbsent) { - mPath = (path != null) ? path : ""; - mContext = context; - mSuccessIfAbsent = successIfAbsent; - } - - - @Override - protected RemoteOperationResult run(WebdavClient client) { - if (!isOnline()) { - return new RemoteOperationResult(RemoteOperationResult.ResultCode.NO_NETWORK_CONNECTION); - } - RemoteOperationResult result = null; - HeadMethod head = null; - try { - head = new HeadMethod(client.getBaseUri() + WebdavUtils.encodePath(mPath)); - int status = client.executeMethod(head, TIMEOUT, TIMEOUT); - client.exhaustResponse(head.getResponseBodyAsStream()); - boolean success = (status == HttpStatus.SC_OK && !mSuccessIfAbsent) || (status == HttpStatus.SC_NOT_FOUND && mSuccessIfAbsent); - result = new RemoteOperationResult(success, status, head.getResponseHeaders()); - Log_OC.d(TAG, "Existence check for " + client.getBaseUri() + WebdavUtils.encodePath(mPath) + " targeting for " + (mSuccessIfAbsent ? " absence " : " existence ") + "finished with HTTP status " + status + (!success?"(FAIL)":"")); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Existence check for " + client.getBaseUri() + WebdavUtils.encodePath(mPath) + " targeting for " + (mSuccessIfAbsent ? " absence " : " existence ") + ": " + result.getLogMessage(), result.getException()); - - } finally { - if (head != null) - head.releaseConnection(); - } - return result; - } - - private boolean isOnline() { - ConnectivityManager cm = (ConnectivityManager) mContext - .getSystemService(Context.CONNECTIVITY_SERVICE); - return cm != null && cm.getActiveNetworkInfo() != null - && cm.getActiveNetworkInfo().isConnectedOrConnecting(); - } - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/OAuth2GetAccessToken.java b/src/de/mobilcom/debitel/cloud/android/operations/OAuth2GetAccessToken.java deleted file mode 100644 index 632efd83..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/OAuth2GetAccessToken.java +++ /dev/null @@ -1,174 +0,0 @@ -package de.mobilcom.debitel.cloud.android.operations; - -import java.util.HashMap; -import java.util.Map; - -import org.apache.commons.httpclient.methods.PostMethod; -import org.apache.commons.httpclient.NameValuePair; -import org.json.JSONException; -import org.json.JSONObject; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.authentication.OAuth2Constants; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult.ResultCode; - -import eu.alefzero.webdav.WebdavClient; - -public class OAuth2GetAccessToken extends RemoteOperation { - - private static final String TAG = OAuth2GetAccessToken.class.getSimpleName(); - - private String mClientId; - private String mRedirectUri; - private String mGrantType; - - private String mOAuth2AuthorizationResponse; - private Map mOAuth2ParsedAuthorizationResponse; - private Map mResultTokenMap; - - - public OAuth2GetAccessToken(String clientId, String redirectUri, String grantType, String oAuth2AuthorizationResponse) { - mClientId = clientId; - mRedirectUri = redirectUri; - mGrantType = grantType; - mOAuth2AuthorizationResponse = oAuth2AuthorizationResponse; - mOAuth2ParsedAuthorizationResponse = new HashMap(); - mResultTokenMap = null; - } - - - public Map getOauth2AutorizationResponse() { - return mOAuth2ParsedAuthorizationResponse; - } - - public Map getResultTokenMap() { - return mResultTokenMap; - } - - @Override - protected RemoteOperationResult run(WebdavClient client) { - RemoteOperationResult result = null; - PostMethod postMethod = null; - - try { - parseAuthorizationResponse(); - if (mOAuth2ParsedAuthorizationResponse.keySet().contains(OAuth2Constants.KEY_ERROR)) { - if (OAuth2Constants.VALUE_ERROR_ACCESS_DENIED.equals(mOAuth2ParsedAuthorizationResponse.get(OAuth2Constants.KEY_ERROR))) { - result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR_ACCESS_DENIED); - } else { - result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); - } - } - - if (result == null) { - NameValuePair[] nameValuePairs = new NameValuePair[4]; - nameValuePairs[0] = new NameValuePair(OAuth2Constants.KEY_GRANT_TYPE, mGrantType); - nameValuePairs[1] = new NameValuePair(OAuth2Constants.KEY_CODE, mOAuth2ParsedAuthorizationResponse.get(OAuth2Constants.KEY_CODE)); - nameValuePairs[2] = new NameValuePair(OAuth2Constants.KEY_REDIRECT_URI, mRedirectUri); - nameValuePairs[3] = new NameValuePair(OAuth2Constants.KEY_CLIENT_ID, mClientId); - //nameValuePairs[4] = new NameValuePair(OAuth2Constants.KEY_SCOPE, mOAuth2ParsedAuthorizationResponse.get(OAuth2Constants.KEY_SCOPE)); - - postMethod = new PostMethod(client.getBaseUri().toString()); - postMethod.setRequestBody(nameValuePairs); - int status = client.executeMethod(postMethod); - - String response = postMethod.getResponseBodyAsString(); - if (response != null && response.length() > 0) { - JSONObject tokenJson = new JSONObject(response); - parseAccessTokenResult(tokenJson); - if (mResultTokenMap.get(OAuth2Constants.KEY_ERROR) != null || mResultTokenMap.get(OAuth2Constants.KEY_ACCESS_TOKEN) == null) { - result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); - - } else { - result = new RemoteOperationResult(true, status, postMethod.getResponseHeaders()); - } - - } else { - client.exhaustResponse(postMethod.getResponseBodyAsStream()); - result = new RemoteOperationResult(false, status, postMethod.getResponseHeaders()); - } - } - - } catch (Exception e) { - result = new RemoteOperationResult(e); - - } finally { - if (postMethod != null) - postMethod.releaseConnection(); // let the connection available for other methods - - if (result.isSuccess()) { - Log_OC.i(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage()); - - } else if (result.getException() != null) { - Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage(), result.getException()); - - } else if (result.getCode() == ResultCode.OAUTH2_ERROR) { - Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + ((mResultTokenMap != null) ? mResultTokenMap.get(OAuth2Constants.KEY_ERROR) : "NULL")); - - } else { - Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage()); - } - } - - return result; - } - - - private void parseAuthorizationResponse() { - String[] pairs = mOAuth2AuthorizationResponse.split("&"); - int i = 0; - String key = ""; - String value = ""; - StringBuilder sb = new StringBuilder(); - while (pairs.length > i) { - int j = 0; - String[] part = pairs[i].split("="); - while (part.length > j) { - String p = part[j]; - if (j == 0) { - key = p; - sb.append(key + " = "); - } else if (j == 1) { - value = p; - mOAuth2ParsedAuthorizationResponse.put(key, value); - sb.append(value + "\n"); - } - - Log_OC.v(TAG, "[" + i + "," + j + "] = " + p); - j++; - } - i++; - } - } - - - private void parseAccessTokenResult (JSONObject tokenJson) throws JSONException { - mResultTokenMap = new HashMap(); - - if (tokenJson.has(OAuth2Constants.KEY_ACCESS_TOKEN)) { - mResultTokenMap.put(OAuth2Constants.KEY_ACCESS_TOKEN, tokenJson.getString(OAuth2Constants.KEY_ACCESS_TOKEN)); - } - if (tokenJson.has(OAuth2Constants.KEY_TOKEN_TYPE)) { - mResultTokenMap.put(OAuth2Constants.KEY_TOKEN_TYPE, tokenJson.getString(OAuth2Constants.KEY_TOKEN_TYPE)); - } - if (tokenJson.has(OAuth2Constants.KEY_EXPIRES_IN)) { - mResultTokenMap.put(OAuth2Constants.KEY_EXPIRES_IN, tokenJson.getString(OAuth2Constants.KEY_EXPIRES_IN)); - } - if (tokenJson.has(OAuth2Constants.KEY_REFRESH_TOKEN)) { - mResultTokenMap.put(OAuth2Constants.KEY_REFRESH_TOKEN, tokenJson.getString(OAuth2Constants.KEY_REFRESH_TOKEN)); - } - if (tokenJson.has(OAuth2Constants.KEY_SCOPE)) { - mResultTokenMap.put(OAuth2Constants.KEY_SCOPE, tokenJson.getString(OAuth2Constants.KEY_SCOPE)); - } - if (tokenJson.has(OAuth2Constants.KEY_ERROR)) { - mResultTokenMap.put(OAuth2Constants.KEY_ERROR, tokenJson.getString(OAuth2Constants.KEY_ERROR)); - } - if (tokenJson.has(OAuth2Constants.KEY_ERROR_DESCRIPTION)) { - mResultTokenMap.put(OAuth2Constants.KEY_ERROR_DESCRIPTION, tokenJson.getString(OAuth2Constants.KEY_ERROR_DESCRIPTION)); - } - if (tokenJson.has(OAuth2Constants.KEY_ERROR_URI)) { - mResultTokenMap.put(OAuth2Constants.KEY_ERROR_URI, tokenJson.getString(OAuth2Constants.KEY_ERROR_URI)); - } - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/OnRemoteOperationListener.java b/src/de/mobilcom/debitel/cloud/android/operations/OnRemoteOperationListener.java deleted file mode 100644 index 3bd183eb..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/OnRemoteOperationListener.java +++ /dev/null @@ -1,25 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.operations; - -public interface OnRemoteOperationListener { - - void onRemoteOperationFinish(RemoteOperation caller, RemoteOperationResult result); - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/OperationCancelledException.java b/src/de/mobilcom/debitel/cloud/android/operations/OperationCancelledException.java deleted file mode 100644 index 8db7ee85..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/OperationCancelledException.java +++ /dev/null @@ -1,28 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.operations; - -public class OperationCancelledException extends Exception { - - /** - * Generated serial version - to avoid Java warning - */ - private static final long serialVersionUID = -6350981497740424983L; - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/OwnCloudServerCheckOperation.java b/src/de/mobilcom/debitel/cloud/android/operations/OwnCloudServerCheckOperation.java deleted file mode 100644 index fbf097f8..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/OwnCloudServerCheckOperation.java +++ /dev/null @@ -1,137 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.operations; - -import org.apache.commons.httpclient.HttpStatus; -import org.apache.commons.httpclient.methods.GetMethod; -import org.json.JSONException; -import org.json.JSONObject; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; -import de.mobilcom.debitel.cloud.android.utils.OwnCloudVersion; - -import eu.alefzero.webdav.WebdavClient; -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.Uri; - -public class OwnCloudServerCheckOperation extends RemoteOperation { - - /** Maximum time to wait for a response from the server when the connection is being tested, in MILLISECONDs. */ - public static final int TRY_CONNECTION_TIMEOUT = 5000; - - private static final String TAG = OwnCloudServerCheckOperation.class.getSimpleName(); - - private String mUrl; - private RemoteOperationResult mLatestResult; - private Context mContext; - private OwnCloudVersion mOCVersion; - - public OwnCloudServerCheckOperation(String url, Context context) { - mUrl = url; - mContext = context; - mOCVersion = null; - } - - public OwnCloudVersion getDiscoveredVersion() { - return mOCVersion; - } - - private boolean tryConnection(WebdavClient wc, String urlSt) { - boolean retval = false; - GetMethod get = null; - try { - get = new GetMethod(urlSt); - int status = wc.executeMethod(get, TRY_CONNECTION_TIMEOUT, TRY_CONNECTION_TIMEOUT); - String response = get.getResponseBodyAsString(); - if (status == HttpStatus.SC_OK) { - JSONObject json = new JSONObject(response); - if (!json.getBoolean("installed")) { - mLatestResult = new RemoteOperationResult(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED); - } else { - mOCVersion = new OwnCloudVersion(json.getString("version")); - if (!mOCVersion.isVersionValid()) { - mLatestResult = new RemoteOperationResult(RemoteOperationResult.ResultCode.BAD_OC_VERSION); - - } else { - mLatestResult = new RemoteOperationResult(urlSt.startsWith("https://") ? - RemoteOperationResult.ResultCode.OK_SSL : - RemoteOperationResult.ResultCode.OK_NO_SSL - ); - - retval = true; - } - } - - } else { - mLatestResult = new RemoteOperationResult(false, status, get.getResponseHeaders()); - } - - } catch (JSONException e) { - mLatestResult = new RemoteOperationResult(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED); - - } catch (Exception e) { - mLatestResult = new RemoteOperationResult(e); - - } finally { - if (get != null) - get.releaseConnection(); - } - - if (mLatestResult.isSuccess()) { - Log_OC.i(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage()); - - } else if (mLatestResult.getException() != null) { - Log_OC.e(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage(), mLatestResult.getException()); - - } else { - Log_OC.e(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage()); - } - - return retval; - } - - private boolean isOnline() { - ConnectivityManager cm = (ConnectivityManager) mContext - .getSystemService(Context.CONNECTIVITY_SERVICE); - return cm != null && cm.getActiveNetworkInfo() != null - && cm.getActiveNetworkInfo().isConnectedOrConnecting(); - } - - @Override - protected RemoteOperationResult run(WebdavClient client) { - if (!isOnline()) { - return new RemoteOperationResult(RemoteOperationResult.ResultCode.NO_NETWORK_CONNECTION); - } - if (mUrl.startsWith("http://") || mUrl.startsWith("https://")) { - tryConnection(client, mUrl + AccountUtils.STATUS_PATH); - - } else { - client.setBaseUri(Uri.parse("https://" + mUrl + AccountUtils.STATUS_PATH)); - boolean httpsSuccess = tryConnection(client, "https://" + mUrl + AccountUtils.STATUS_PATH); - if (!httpsSuccess && !mLatestResult.isSslRecoverableException()) { - Log_OC.d(TAG, "establishing secure connection failed, trying non secure connection"); - client.setBaseUri(Uri.parse("http://" + mUrl + AccountUtils.STATUS_PATH)); - tryConnection(client, "http://" + mUrl + AccountUtils.STATUS_PATH); - } - } - return mLatestResult; - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/RemoteOperation.java b/src/de/mobilcom/debitel/cloud/android/operations/RemoteOperation.java deleted file mode 100644 index 19bbfb28..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/RemoteOperation.java +++ /dev/null @@ -1,292 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.operations; - -import java.io.IOException; - -import org.apache.commons.httpclient.Credentials; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.network.BearerCredentials; -import de.mobilcom.debitel.cloud.android.network.OwnCloudClientUtils; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult.ResultCode; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AccountsException; -import android.app.Activity; -import android.content.Context; -import android.os.Handler; - -import eu.alefzero.webdav.WebdavClient; - -/** - * Operation which execution involves one or several interactions with an ownCloud server. - * - * Provides methods to execute the operation both synchronously or asynchronously. - * - * @author David A. Velasco - */ -public abstract class RemoteOperation implements Runnable { - - private static final String TAG = RemoteOperation.class.getSimpleName(); - - /** ownCloud account in the remote ownCloud server to operate */ - private Account mAccount = null; - - /** Android Application context */ - private Context mContext = null; - - /** Object to interact with the remote server */ - private WebdavClient mClient = null; - - /** Callback object to notify about the execution of the remote operation */ - private OnRemoteOperationListener mListener = null; - - /** Handler to the thread where mListener methods will be called */ - private Handler mListenerHandler = null; - - /** Activity */ - private Activity mCallerActivity; - - - /** - * Abstract method to implement the operation in derived classes. - */ - protected abstract RemoteOperationResult run(WebdavClient client); - - - /** - * Synchronously executes the remote operation on the received ownCloud account. - * - * Do not call this method from the main thread. - * - * This method should be used whenever an ownCloud account is available, instead of {@link #execute(WebdavClient)}. - * - * @param account ownCloud account in remote ownCloud server to reach during the execution of the operation. - * @param context Android context for the component calling the method. - * @return Result of the operation. - */ - public final RemoteOperationResult execute(Account account, Context context) { - if (account == null) - throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Account"); - if (context == null) - throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Context"); - mAccount = account; - mContext = context.getApplicationContext(); - try { - mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext); - } catch (Exception e) { - Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, e); - return new RemoteOperationResult(e); - } - return run(mClient); - } - - - /** - * Synchronously executes the remote operation - * - * Do not call this method from the main thread. - * - * @param client Client object to reach an ownCloud server during the execution of the operation. - * @return Result of the operation. - */ - public final RemoteOperationResult execute(WebdavClient client) { - if (client == null) - throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient"); - mClient = client; - return run(client); - } - - - /** - * Asynchronously executes the remote operation - * - * This method should be used whenever an ownCloud account is available, instead of {@link #execute(WebdavClient)}. - * - * @param account ownCloud account in remote ownCloud server to reach during the execution of the operation. - * @param context Android context for the component calling the method. - * @param listener Listener to be notified about the execution of the operation. - * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. - * @return Thread were the remote operation is executed. - */ - public final Thread execute(Account account, Context context, OnRemoteOperationListener listener, Handler listenerHandler, Activity callerActivity) { - if (account == null) - throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Account"); - if (context == null) - throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Context"); - mAccount = account; - mContext = context.getApplicationContext(); - mCallerActivity = callerActivity; - mClient = null; // the client instance will be created from mAccount and mContext in the runnerThread to create below - - if (listener == null) { - throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a listener to notiy the result"); - } - mListener = listener; - - if (listenerHandler == null) { - throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a handler to the listener's thread"); - } - mListenerHandler = listenerHandler; - - Thread runnerThread = new Thread(this); - runnerThread.start(); - return runnerThread; - } - - - /** - * Asynchronously executes the remote operation - * - * @param client Client object to reach an ownCloud server during the execution of the operation. - * @param listener Listener to be notified about the execution of the operation. - * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. - * @return Thread were the remote operation is executed. - */ - public final Thread execute(WebdavClient client, OnRemoteOperationListener listener, Handler listenerHandler) { - if (client == null) { - throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient"); - } - mClient = client; - - if (listener == null) { - throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a listener to notiy the result"); - } - mListener = listener; - - if (listenerHandler == null) { - throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a handler to the listener's thread"); - } - mListenerHandler = listenerHandler; - - Thread runnerThread = new Thread(this); - runnerThread.start(); - return runnerThread; - } - - /** - * Synchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient)} - * - * @param listener Listener to be notified about the execution of the operation. - * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. - * @return Thread were the remote operation is executed. - */ - public final RemoteOperationResult retry() { - return execute(mClient); - } - - /** - * Asynchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)} - * - * @param listener Listener to be notified about the execution of the operation. - * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. - * @return Thread were the remote operation is executed. - */ - public final Thread retry(OnRemoteOperationListener listener, Handler listenerHandler) { - return execute(mClient, listener, listenerHandler); - } - - - /** - * Asynchronous execution of the operation - * started by {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)}, - * and result posting. - * - * TODO refactor && clean the code; now it's a mess - */ - @Override - public final void run() { - RemoteOperationResult result = null; - boolean repeat = false; - do { - try{ - if (mClient == null) { - if (mAccount != null && mContext != null) { - if (mCallerActivity != null) { - mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext, mCallerActivity); - } else { - mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext); - } - } else { - throw new IllegalStateException("Trying to run a remote operation asynchronously with no client instance or account"); - } - } - - } catch (IOException e) { - Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, new AccountsException("I/O exception while trying to authorize the account", e)); - result = new RemoteOperationResult(e); - - } catch (AccountsException e) { - Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, e); - result = new RemoteOperationResult(e); - } - - if (result == null) - result = run(mClient); - - repeat = false; - if (mCallerActivity != null && mAccount != null && mContext != null && !result.isSuccess() && -// (result.getCode() == ResultCode.UNAUTHORIZED || (result.isTemporalRedirection() && result.isIdPRedirection()))) { - (result.getCode() == ResultCode.UNAUTHORIZED || result.isIdPRedirection())) { - /// possible fail due to lack of authorization in an operation performed in foreground - Credentials cred = mClient.getCredentials(); - String ssoSessionCookie = mClient.getSsoSessionCookie(); - if (cred != null || ssoSessionCookie != null) { - /// confirmed : unauthorized operation - AccountManager am = AccountManager.get(mContext); - boolean bearerAuthorization = (cred != null && cred instanceof BearerCredentials); - boolean samlBasedSsoAuthorization = (cred == null && ssoSessionCookie != null); - if (bearerAuthorization) { - am.invalidateAuthToken(MainApp.getAccountType(), ((BearerCredentials)cred).getAccessToken()); - } else if (samlBasedSsoAuthorization ) { - am.invalidateAuthToken(MainApp.getAccountType(), ssoSessionCookie); - } else { - am.clearPassword(mAccount); - } - mClient = null; - repeat = true; // when repeated, the creation of a new OwnCloudClient after erasing the saved credentials will trigger the login activity - result = null; - } - } - } while (repeat); - - final RemoteOperationResult resultToSend = result; - if (mListenerHandler != null && mListener != null) { - mListenerHandler.post(new Runnable() { - @Override - public void run() { - mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend); - } - }); - } - } - - - /** - * Returns the current client instance to access the remote server. - * - * @return Current client instance to access the remote server. - */ - public final WebdavClient getClient() { - return mClient; - } - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/RemoteOperationResult.java b/src/de/mobilcom/debitel/cloud/android/operations/RemoteOperationResult.java deleted file mode 100644 index c5b7dbab..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/RemoteOperationResult.java +++ /dev/null @@ -1,337 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.operations; - -import java.io.IOException; -import java.io.Serializable; -import java.net.MalformedURLException; -import java.net.SocketException; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; - -import javax.net.ssl.SSLException; - -import org.apache.commons.httpclient.ConnectTimeoutException; -import org.apache.commons.httpclient.Header; -import org.apache.commons.httpclient.HttpException; -import org.apache.commons.httpclient.HttpStatus; -import org.apache.jackrabbit.webdav.DavException; - -import android.accounts.Account; -import android.accounts.AccountsException; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils.AccountNotFoundException; -import de.mobilcom.debitel.cloud.android.network.CertificateCombinedException; - -/** - * The result of a remote operation required to an ownCloud server. - * - * Provides a common classification of remote operation results for all the - * application. - * - * @author David A. Velasco - */ -public class RemoteOperationResult implements Serializable { - - /** Generated - should be refreshed every time the class changes!! */ - private static final long serialVersionUID = -4415103901492836870L; - - - - private static final String TAG = "RemoteOperationResult"; - - public enum ResultCode { - OK, - OK_SSL, - OK_NO_SSL, - UNHANDLED_HTTP_CODE, - UNAUTHORIZED, - FILE_NOT_FOUND, - INSTANCE_NOT_CONFIGURED, - UNKNOWN_ERROR, - WRONG_CONNECTION, - TIMEOUT, - INCORRECT_ADDRESS, - HOST_NOT_AVAILABLE, - NO_NETWORK_CONNECTION, - SSL_ERROR, - SSL_RECOVERABLE_PEER_UNVERIFIED, - BAD_OC_VERSION, - CANCELLED, - INVALID_LOCAL_FILE_NAME, - INVALID_OVERWRITE, - CONFLICT, - OAUTH2_ERROR, - SYNC_CONFLICT, - LOCAL_STORAGE_FULL, - LOCAL_STORAGE_NOT_MOVED, - LOCAL_STORAGE_NOT_COPIED, - OAUTH2_ERROR_ACCESS_DENIED, - QUOTA_EXCEEDED, - ACCOUNT_NOT_FOUND, - ACCOUNT_EXCEPTION, - ACCOUNT_NOT_NEW, - ACCOUNT_NOT_THE_SAME - } - - private boolean mSuccess = false; - private int mHttpCode = -1; - private Exception mException = null; - private ResultCode mCode = ResultCode.UNKNOWN_ERROR; - private String mRedirectedLocation; - - public RemoteOperationResult(ResultCode code) { - mCode = code; - mSuccess = (code == ResultCode.OK || code == ResultCode.OK_SSL || code == ResultCode.OK_NO_SSL); - } - - private RemoteOperationResult(boolean success, int httpCode) { - mSuccess = success; - mHttpCode = httpCode; - - if (success) { - mCode = ResultCode.OK; - - } else if (httpCode > 0) { - switch (httpCode) { - case HttpStatus.SC_UNAUTHORIZED: - mCode = ResultCode.UNAUTHORIZED; - break; - case HttpStatus.SC_NOT_FOUND: - mCode = ResultCode.FILE_NOT_FOUND; - break; - case HttpStatus.SC_INTERNAL_SERVER_ERROR: - mCode = ResultCode.INSTANCE_NOT_CONFIGURED; - break; - case HttpStatus.SC_CONFLICT: - mCode = ResultCode.CONFLICT; - break; - case HttpStatus.SC_INSUFFICIENT_STORAGE: - mCode = ResultCode.QUOTA_EXCEEDED; - break; - default: - mCode = ResultCode.UNHANDLED_HTTP_CODE; - Log_OC.d(TAG, "RemoteOperationResult has processed UNHANDLED_HTTP_CODE: " + httpCode); - } - } - } - - public RemoteOperationResult(boolean success, int httpCode, Header[] headers) { - this(success, httpCode); - if (headers != null) { - Header current; - for (int i=0; i= HttpStatus.SC_INTERNAL_SERVER_ERROR); - } - - public boolean isException() { - return (mException != null); - } - - public boolean isTemporalRedirection() { - return (mHttpCode == 302 || mHttpCode == 307); - } - - public String getRedirectedLocation() { - return mRedirectedLocation; - } - - public boolean isIdPRedirection() { - return (mRedirectedLocation != null && - (mRedirectedLocation.toUpperCase().contains("SAML") || - mRedirectedLocation.toLowerCase().contains("wayf"))); - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/RemoveFileOperation.java b/src/de/mobilcom/debitel/cloud/android/operations/RemoveFileOperation.java deleted file mode 100644 index 03c4cb88..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/RemoveFileOperation.java +++ /dev/null @@ -1,105 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.operations; - -import org.apache.commons.httpclient.HttpStatus; -import org.apache.jackrabbit.webdav.client.methods.DeleteMethod; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.datamodel.DataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; - -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; - -/** - * Remote operation performing the removal of a remote file or folder in the ownCloud server. - * - * @author David A. Velasco - */ -public class RemoveFileOperation extends RemoteOperation { - - private static final String TAG = RemoveFileOperation.class.getSimpleName(); - - private static final int REMOVE_READ_TIMEOUT = 10000; - private static final int REMOVE_CONNECTION_TIMEOUT = 5000; - - OCFile mFileToRemove; - boolean mDeleteLocalCopy; - DataStorageManager mDataStorageManager; - - - /** - * Constructor - * - * @param fileToRemove OCFile instance describing the remote file or folder to remove from the server - * @param deleteLocalCopy When 'true', and a local copy of the file exists, it is also removed. - * @param storageManager Reference to the local database corresponding to the account where the file is contained. - */ - public RemoveFileOperation(OCFile fileToRemove, boolean deleteLocalCopy, DataStorageManager storageManager) { - mFileToRemove = fileToRemove; - mDeleteLocalCopy = deleteLocalCopy; - mDataStorageManager = storageManager; - } - - - /** - * Getter for the file to remove (or removed, if the operation was successfully performed). - * - * @return File to remove or already removed. - */ - public OCFile getFile() { - return mFileToRemove; - } - - - /** - * Performs the remove operation - * - * @param client Client object to communicate with the remote ownCloud server. - */ - @Override - protected RemoteOperationResult run(WebdavClient client) { - RemoteOperationResult result = null; - DeleteMethod delete = null; - try { - delete = new DeleteMethod(client.getBaseUri() + WebdavUtils.encodePath(mFileToRemove.getRemotePath())); - int status = client.executeMethod(delete, REMOVE_READ_TIMEOUT, REMOVE_CONNECTION_TIMEOUT); - if (delete.succeeded() || status == HttpStatus.SC_NOT_FOUND) { - if (mFileToRemove.isDirectory()) { - mDataStorageManager.removeDirectory(mFileToRemove, true, mDeleteLocalCopy); - } else { - mDataStorageManager.removeFile(mFileToRemove, mDeleteLocalCopy); - } - } - delete.getResponseBodyAsString(); // exhaust the response, although not interesting - result = new RemoteOperationResult((delete.succeeded() || status == HttpStatus.SC_NOT_FOUND), status, delete.getResponseHeaders()); - Log_OC.i(TAG, "Remove " + mFileToRemove.getRemotePath() + ": " + result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Remove " + mFileToRemove.getRemotePath() + ": " + result.getLogMessage(), e); - - } finally { - if (delete != null) - delete.releaseConnection(); - } - return result; - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/RenameFileOperation.java b/src/de/mobilcom/debitel/cloud/android/operations/RenameFileOperation.java deleted file mode 100644 index 60e1b035..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/RenameFileOperation.java +++ /dev/null @@ -1,247 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.operations; - -import java.io.File; -import java.io.IOException; - -import org.apache.jackrabbit.webdav.client.methods.DavMethodBase; -//import org.apache.jackrabbit.webdav.client.methods.MoveMethod; - -import android.accounts.Account; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.datamodel.DataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult.ResultCode; -import de.mobilcom.debitel.cloud.android.utils.FileStorageUtils; - -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; - -/** - * Remote operation performing the rename of a remote file (or folder?) in the ownCloud server. - * - * @author David A. Velasco - */ -public class RenameFileOperation extends RemoteOperation { - - private static final String TAG = RenameFileOperation.class.getSimpleName(); - - private static final int RENAME_READ_TIMEOUT = 10000; - private static final int RENAME_CONNECTION_TIMEOUT = 5000; - - - private OCFile mFile; - private Account mAccount; - private String mNewName; - private String mNewRemotePath; - private DataStorageManager mStorageManager; - - - /** - * Constructor - * - * @param file OCFile instance describing the remote file or folder to rename - * @param account OwnCloud account containing the remote file - * @param newName New name to set as the name of file. - * @param storageManager Reference to the local database corresponding to the account where the file is contained. - */ - public RenameFileOperation(OCFile file, Account account, String newName, DataStorageManager storageManager) { - mFile = file; - mAccount = account; - mNewName = newName; - mNewRemotePath = null; - mStorageManager = storageManager; - } - - public OCFile getFile() { - return mFile; - } - - - /** - * Performs the rename operation. - * - * @param client Client object to communicate with the remote ownCloud server. - */ - @Override - protected RemoteOperationResult run(WebdavClient client) { - RemoteOperationResult result = null; - - LocalMoveMethod move = null; - mNewRemotePath = null; - try { - if (mNewName.equals(mFile.getFileName())) { - return new RemoteOperationResult(ResultCode.OK); - } - - String parent = (new File(mFile.getRemotePath())).getParent(); - parent = (parent.endsWith(OCFile.PATH_SEPARATOR)) ? parent : parent + OCFile.PATH_SEPARATOR; - mNewRemotePath = parent + mNewName; - if (mFile.isDirectory()) { - mNewRemotePath += OCFile.PATH_SEPARATOR; - } - - // check if the new name is valid in the local file system - if (!isValidNewName()) { - return new RemoteOperationResult(ResultCode.INVALID_LOCAL_FILE_NAME); - } - - // check if a file with the new name already exists - if (client.existsFile(mNewRemotePath) || // remote check could fail by network failure. by indeterminate behavior of HEAD for folders ... - mStorageManager.getFileByPath(mNewRemotePath) != null) { // ... so local check is convenient - return new RemoteOperationResult(ResultCode.INVALID_OVERWRITE); - } - move = new LocalMoveMethod( client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath()), - client.getBaseUri() + WebdavUtils.encodePath(mNewRemotePath)); - int status = client.executeMethod(move, RENAME_READ_TIMEOUT, RENAME_CONNECTION_TIMEOUT); - if (move.succeeded()) { - - if (mFile.isDirectory()) { - saveLocalDirectory(); - - } else { - saveLocalFile(); - - } - - /* - *} else if (mFile.isDirectory() && (status == 207 || status >= 500)) { - * // TODO - * // if server fails in the rename of a folder, some children files could have been moved to a folder with the new name while some others - * // stayed in the old folder; - * // - * // easiest and heaviest solution is synchronizing the parent folder (or the full account); - * // - * // a better solution is synchronizing the folders with the old and new names; - *} - */ - - } - - move.getResponseBodyAsString(); // exhaust response, although not interesting - result = new RemoteOperationResult(move.succeeded(), status, move.getResponseHeaders()); - Log_OC.i(TAG, "Rename " + mFile.getRemotePath() + " to " + mNewRemotePath + ": " + result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Rename " + mFile.getRemotePath() + " to " + ((mNewRemotePath==null) ? mNewName : mNewRemotePath) + ": " + result.getLogMessage(), e); - - } finally { - if (move != null) - move.releaseConnection(); - } - return result; - } - - - private void saveLocalDirectory() { - mStorageManager.moveDirectory(mFile, mNewRemotePath); - String localPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); - File localDir = new File(localPath); - if (localDir.exists()) { - localDir.renameTo(new File(FileStorageUtils.getSavePath(mAccount.name) + mNewRemotePath)); - // TODO - if renameTo fails, children files that are already down will result unlinked - } - } - - private void saveLocalFile() { - mFile.setFileName(mNewName); - - // try to rename the local copy of the file - if (mFile.isDown()) { - File f = new File(mFile.getStoragePath()); - String parentStoragePath = f.getParent(); - if (!parentStoragePath.endsWith(File.separator)) - parentStoragePath += File.separator; - if (f.renameTo(new File(parentStoragePath + mNewName))) { - mFile.setStoragePath(parentStoragePath + mNewName); - } - // else - NOTHING: the link to the local file is kept although the local name can't be updated - // TODO - study conditions when this could be a problem - } - - mStorageManager.saveFile(mFile); - } - - /** - * Checks if the new name to set is valid in the file system - * - * The only way to be sure is trying to create a file with that name. It's made in the temporal directory - * for downloads, out of any account, and then removed. - * - * IMPORTANT: The test must be made in the same file system where files are download. The internal storage - * could be formatted with a different file system. - * - * TODO move this method, and maybe FileDownload.get***Path(), to a class with utilities specific for the interactions with the file system - * - * @return 'True' if a temporal file named with the name to set could be created in the file system where - * local files are stored. - * @throws IOException When the temporal folder can not be created. - */ - private boolean isValidNewName() throws IOException { - // check tricky names - if (mNewName == null || mNewName.length() <= 0 || mNewName.contains(File.separator) || mNewName.contains("%")) { - return false; - } - // create a test file - String tmpFolderName = FileStorageUtils.getTemporalPath(""); - File testFile = new File(tmpFolderName + mNewName); - File tmpFolder = testFile.getParentFile(); - tmpFolder.mkdirs(); - if (!tmpFolder.isDirectory()) { - throw new IOException("Unexpected error: temporal directory could not be created"); - } - try { - testFile.createNewFile(); // return value is ignored; it could be 'false' because the file already existed, that doesn't invalidate the name - } catch (IOException e) { - Log_OC.i(TAG, "Test for validity of name " + mNewName + " in the file system failed"); - return false; - } - boolean result = (testFile.exists() && testFile.isFile()); - - // cleaning ; result is ignored, since there is not much we could do in case of failure, but repeat and repeat... - testFile.delete(); - - return result; - } - - - // move operation - private class LocalMoveMethod extends DavMethodBase { - - public LocalMoveMethod(String uri, String dest) { - super(uri); - addRequestHeader(new org.apache.commons.httpclient.Header("Destination", dest)); - } - - @Override - public String getName() { - return "MOVE"; - } - - @Override - protected boolean isSuccess(int status) { - return status == 201 || status == 204; - } - - } - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/SynchronizeFileOperation.java b/src/de/mobilcom/debitel/cloud/android/operations/SynchronizeFileOperation.java deleted file mode 100644 index bd5637ea..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/SynchronizeFileOperation.java +++ /dev/null @@ -1,234 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.operations; - -import org.apache.http.HttpStatus; -import org.apache.jackrabbit.webdav.DavConstants; -import org.apache.jackrabbit.webdav.MultiStatus; -import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; - -import android.accounts.Account; -import android.content.Context; -import android.content.Intent; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.datamodel.DataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.files.services.FileDownloader; -import de.mobilcom.debitel.cloud.android.files.services.FileUploader; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult.ResultCode; - -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavEntry; -import eu.alefzero.webdav.WebdavUtils; - -public class SynchronizeFileOperation extends RemoteOperation { - - private String TAG = SynchronizeFileOperation.class.getSimpleName(); - private static final int SYNC_READ_TIMEOUT = 10000; - private static final int SYNC_CONNECTION_TIMEOUT = 5000; - - private OCFile mLocalFile; - private OCFile mServerFile; - private DataStorageManager mStorageManager; - private Account mAccount; - private boolean mSyncFileContents; - private boolean mLocalChangeAlreadyKnown; - private Context mContext; - - private boolean mTransferWasRequested = false; - - public SynchronizeFileOperation( - OCFile localFile, - OCFile serverFile, // make this null to let the operation checks the server; added to reuse info from SynchronizeFolderOperation - DataStorageManager storageManager, - Account account, - boolean syncFileContents, - boolean localChangeAlreadyKnown, - Context context) { - - mLocalFile = localFile; - mServerFile = serverFile; - mStorageManager = storageManager; - mAccount = account; - mSyncFileContents = syncFileContents; - mLocalChangeAlreadyKnown = localChangeAlreadyKnown; - mContext = context; - } - - - @Override - protected RemoteOperationResult run(WebdavClient client) { - - PropFindMethod propfind = null; - RemoteOperationResult result = null; - mTransferWasRequested = false; - try { - if (!mLocalFile.isDown()) { - /// easy decision - requestForDownload(mLocalFile); - result = new RemoteOperationResult(ResultCode.OK); - - } else { - /// local copy in the device -> need to think a bit more before do anything - - if (mServerFile == null) { - /// take the duty of check the server for the current state of the file there - propfind = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mLocalFile.getRemotePath()), - DavConstants.PROPFIND_ALL_PROP, - DavConstants.DEPTH_0); - int status = client.executeMethod(propfind, SYNC_READ_TIMEOUT, SYNC_CONNECTION_TIMEOUT); - boolean isMultiStatus = status == HttpStatus.SC_MULTI_STATUS; - if (isMultiStatus) { - MultiStatus resp = propfind.getResponseBodyAsMultiStatus(); - WebdavEntry we = new WebdavEntry(resp.getResponses()[0], - client.getBaseUri().getPath()); - mServerFile = fillOCFile(we); - mServerFile.setLastSyncDateForProperties(System.currentTimeMillis()); - - } else { - client.exhaustResponse(propfind.getResponseBodyAsStream()); - result = new RemoteOperationResult(false, status, propfind.getResponseHeaders()); - } - } - - if (result == null) { // true if the server was not checked. nothing was wrong with the remote request - - /// check changes in server and local file - boolean serverChanged = false; - if (mServerFile.getEtag() != null) { - serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag())); // TODO could this be dangerous when the user upgrades the server from non-tagged to tagged? - } else { - // server without etags - serverChanged = (mServerFile.getModificationTimestamp() > mLocalFile.getModificationTimestampAtLastSyncForData()); - } - boolean localChanged = (mLocalChangeAlreadyKnown || mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData()); - // TODO this will be always true after the app is upgraded to database version 2; will result in unnecessary uploads - - /// decide action to perform depending upon changes - if (localChanged && serverChanged) { - result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); - - } else if (localChanged) { - if (mSyncFileContents) { - requestForUpload(mLocalFile); - // the local update of file properties will be done by the FileUploader service when the upload finishes - } else { - // NOTHING TO DO HERE: updating the properties of the file in the server without uploading the contents would be stupid; - // So, an instance of SynchronizeFileOperation created with syncFileContents == false is completely useless when we suspect - // that an upload is necessary (for instance, in FileObserverService). - } - result = new RemoteOperationResult(ResultCode.OK); - - } else if (serverChanged) { - if (mSyncFileContents) { - requestForDownload(mLocalFile); // local, not server; we won't to keep the value of keepInSync! - // the update of local data will be done later by the FileUploader service when the upload finishes - } else { - // TODO CHECK: is this really useful in some point in the code? - mServerFile.setKeepInSync(mLocalFile.keepInSync()); - mServerFile.setLastSyncDateForData(mLocalFile.getLastSyncDateForData()); - mServerFile.setStoragePath(mLocalFile.getStoragePath()); - mServerFile.setParentId(mLocalFile.getParentId()); - mStorageManager.saveFile(mServerFile); - - } - result = new RemoteOperationResult(ResultCode.OK); - - } else { - // nothing changed, nothing to do - result = new RemoteOperationResult(ResultCode.OK); - } - - } - - } - - Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " + result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", file " + (mLocalFile != null ? mLocalFile.getRemotePath() : "NULL") + ": " + result.getLogMessage(), result.getException()); - - } finally { - if (propfind != null) - propfind.releaseConnection(); - } - return result; - } - - - /** - * Requests for an upload to the FileUploader service - * - * @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); - /*i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath); // 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); - mContext.startService(i); - mTransferWasRequested = true; - } - - - /** - * Requests for a download to the FileDownloader service - * - * @param file OCFile object representing the file to download - */ - private void requestForDownload(OCFile file) { - Intent i = new Intent(mContext, FileDownloader.class); - i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount); - i.putExtra(FileDownloader.EXTRA_FILE, file); - mContext.startService(i); - mTransferWasRequested = true; - } - - - /** - * Creates and populates a new {@link OCFile} object with the data read from the server. - * - * @param we WebDAV entry read from the server for a WebDAV resource (remote file or folder). - * @return New OCFile instance representing the remote resource described by we. - */ - private OCFile fillOCFile(WebdavEntry we) { - OCFile file = new OCFile(we.decodedPath()); - file.setCreationTimestamp(we.createTimestamp()); - file.setFileLength(we.contentLength()); - file.setMimetype(we.contentType()); - file.setModificationTimestamp(we.modifiedTimestamp()); - return file; - } - - - public boolean transferWasRequested() { - return mTransferWasRequested; - } - - - public OCFile getLocalFile() { - return mLocalFile; - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/SynchronizeFolderOperation.java b/src/de/mobilcom/debitel/cloud/android/operations/SynchronizeFolderOperation.java deleted file mode 100644 index d089f381..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/SynchronizeFolderOperation.java +++ /dev/null @@ -1,364 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.operations; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Vector; - -import org.apache.http.HttpStatus; -import org.apache.jackrabbit.webdav.DavConstants; -import org.apache.jackrabbit.webdav.MultiStatus; -import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; - -import android.accounts.Account; -import android.content.Context; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.datamodel.DataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult.ResultCode; -import de.mobilcom.debitel.cloud.android.utils.FileStorageUtils; - -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavEntry; -import eu.alefzero.webdav.WebdavUtils; - - -/** - * Remote operation performing the synchronization a the contents of a remote folder with the local database - * - * @author David A. Velasco - */ -public class SynchronizeFolderOperation extends RemoteOperation { - - private static final String TAG = SynchronizeFolderOperation.class.getSimpleName(); - - /** Remote folder to synchronize */ - private String mRemotePath; - - /** Timestamp for the synchronization in progress */ - private long mCurrentSyncTime; - - /** Id of the folder to synchronize in the local database */ - private long mParentId; - - /** Access to the local database */ - private DataStorageManager mStorageManager; - - /** Account where the file to synchronize belongs */ - private Account mAccount; - - /** Android context; necessary to send requests to the download service; maybe something to refactor */ - private Context mContext; - - /** Files and folders contained in the synchronized folder */ - private List mChildren; - - private int mConflictsFound; - - private int mFailsInFavouritesFound; - - private Map mForgottenLocalFiles; - - - public SynchronizeFolderOperation( String remotePath, - long currentSyncTime, - long parentId, - DataStorageManager dataStorageManager, - Account account, - Context context ) { - mRemotePath = remotePath; - mCurrentSyncTime = currentSyncTime; - mParentId = parentId; - mStorageManager = dataStorageManager; - mAccount = account; - mContext = context; - mForgottenLocalFiles = new HashMap(); - } - - - public int getConflictsFound() { - return mConflictsFound; - } - - public int getFailsInFavouritesFound() { - return mFailsInFavouritesFound; - } - - public Map getForgottenLocalFiles() { - return mForgottenLocalFiles; - } - - /** - * Returns the list of files and folders contained in the synchronized folder, if called after synchronization is complete. - * - * @return List of files and folders contained in the synchronized folder. - */ - public List getChildren() { - return mChildren; - } - - - @Override - protected RemoteOperationResult run(WebdavClient client) { - RemoteOperationResult result = null; - mFailsInFavouritesFound = 0; - mConflictsFound = 0; - mForgottenLocalFiles.clear(); - - // code before in FileSyncAdapter.fetchData - PropFindMethod query = null; - try { - Log_OC.d(TAG, "Synchronizing " + mAccount.name + ", fetching files in " + mRemotePath); - - // remote request - query = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath), - DavConstants.PROPFIND_ALL_PROP, - DavConstants.DEPTH_1); - int status = client.executeMethod(query); - - // check and process response - /// TODO take into account all the possible status per child-resource - if (isMultiStatus(status)) { - MultiStatus resp = query.getResponseBodyAsMultiStatus(); - - // synchronize properties of the parent folder, if necessary - if (mParentId == DataStorageManager.ROOT_PARENT_ID) { - WebdavEntry we = new WebdavEntry(resp.getResponses()[0], client.getBaseUri().getPath()); - OCFile parent = fillOCFile(we); - mStorageManager.saveFile(parent); - mParentId = parent.getFileId(); - } - - // read contents in folder - List updatedFiles = new Vector(resp.getResponses().length - 1); - List filesToSyncContents = new Vector(); - for (int i = 1; i < resp.getResponses().length; ++i) { - /// new OCFile instance with the data from the server - WebdavEntry we = new WebdavEntry(resp.getResponses()[i], client.getBaseUri().getPath()); - OCFile file = fillOCFile(we); - - /// set data about local state, keeping unchanged former data if existing - file.setLastSyncDateForProperties(mCurrentSyncTime); - OCFile oldFile = mStorageManager.getFileByPath(file.getRemotePath()); - if (oldFile != null) { - file.setKeepInSync(oldFile.keepInSync()); - file.setLastSyncDateForData(oldFile.getLastSyncDateForData()); - file.setModificationTimestampAtLastSyncForData(oldFile.getModificationTimestampAtLastSyncForData()); // must be kept unchanged when the file contents are not updated - checkAndFixForeignStoragePath(oldFile); - file.setStoragePath(oldFile.getStoragePath()); - } - - /// scan default location if local copy of file is not linked in OCFile instance - if (file.getStoragePath() == null && !file.isDirectory()) { - File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); - if (f.exists()) { - file.setStoragePath(f.getAbsolutePath()); - file.setLastSyncDateForData(f.lastModified()); - } - } - - /// prepare content synchronization for kept-in-sync files - if (file.keepInSync()) { - SynchronizeFileOperation operation = new SynchronizeFileOperation( oldFile, - file, - mStorageManager, - mAccount, - true, - false, - mContext - ); - filesToSyncContents.add(operation); - } - - updatedFiles.add(file); - } - - // save updated contents in local database; all at once, trying to get a best performance in database update (not a big deal, indeed) - mStorageManager.saveFiles(updatedFiles); - - // request for the synchronization of files AFTER saving last properties - SynchronizeFileOperation op = null; - RemoteOperationResult contentsResult = null; - for (int i=0; i < filesToSyncContents.size(); i++) { - op = filesToSyncContents.get(i); - contentsResult = op.execute(client); // returns without waiting for upload or download finishes - if (!contentsResult.isSuccess()) { - if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) { - mConflictsFound++; - } else { - mFailsInFavouritesFound++; - if (contentsResult.getException() != null) { - Log_OC.e(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage(), contentsResult.getException()); - } else { - Log_OC.e(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage()); - } - } - } // won't let these fails break the synchronization process - } - - - // removal of obsolete files - mChildren = mStorageManager.getDirectoryContent(mStorageManager.getFileById(mParentId)); - OCFile file; - String currentSavePath = FileStorageUtils.getSavePath(mAccount.name); - for (int i=0; i < mChildren.size(); ) { - file = mChildren.get(i); - if (file.getLastSyncDateForProperties() != mCurrentSyncTime) { - Log_OC.d(TAG, "removing file: " + file); - mStorageManager.removeFile(file, (file.isDown() && file.getStoragePath().startsWith(currentSavePath))); - mChildren.remove(i); - } else { - i++; - } - } - - } else { - client.exhaustResponse(query.getResponseBodyAsStream()); - } - - // prepare result object - if (isMultiStatus(status)) { - if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) { - result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); // should be different result, but will do the job - - } else { - result = new RemoteOperationResult(true, status, query.getResponseHeaders()); - } - } else { - result = new RemoteOperationResult(false, status, query.getResponseHeaders()); - } - - - - } catch (Exception e) { - result = new RemoteOperationResult(e); - - - } finally { - if (query != null) - query.releaseConnection(); // let the connection available for other methods - if (result.isSuccess()) { - Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage()); - } else { - if (result.isException()) { - Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage(), result.getException()); - } else { - Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage()); - } - } - } - - return result; - } - - - public boolean isMultiStatus(int status) { - return (status == HttpStatus.SC_MULTI_STATUS); - } - - - /** - * Creates and populates a new {@link OCFile} object with the data read from the server. - * - * @param we WebDAV entry read from the server for a WebDAV resource (remote file or folder). - * @return New OCFile instance representing the remote resource described by we. - */ - private OCFile fillOCFile(WebdavEntry we) { - OCFile file = new OCFile(we.decodedPath()); - file.setCreationTimestamp(we.createTimestamp()); - file.setFileLength(we.contentLength()); - file.setMimetype(we.contentType()); - file.setModificationTimestamp(we.modifiedTimestamp()); - file.setParentId(mParentId); - return file; - } - - - /** - * Checks the storage path of the OCFile received as parameter. If it's out of the local ownCloud folder, - * tries to copy the file inside it. - * - * If the copy fails, the link to the local file is nullified. The account of forgotten files is kept in - * {@link #mForgottenLocalFiles} - * - * @param file File to check and fix. - */ - private void checkAndFixForeignStoragePath(OCFile file) { - String storagePath = file.getStoragePath(); - String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, file); - if (storagePath != null && !storagePath.equals(expectedPath)) { - /// fix storagePaths out of the local ownCloud folder - File originalFile = new File(storagePath); - if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) { - mForgottenLocalFiles.put(file.getRemotePath(), storagePath); - file.setStoragePath(null); - - } else { - InputStream in = null; - OutputStream out = null; - try { - File expectedFile = new File(expectedPath); - File expectedParent = expectedFile.getParentFile(); - expectedParent.mkdirs(); - if (!expectedParent.isDirectory()) { - throw new IOException("Unexpected error: parent directory could not be created"); - } - expectedFile.createNewFile(); - if (!expectedFile.isFile()) { - throw new IOException("Unexpected error: target file could not be created"); - } - in = new FileInputStream(originalFile); - out = new FileOutputStream(expectedFile); - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0){ - out.write(buf, 0, len); - } - file.setStoragePath(expectedPath); - - } catch (Exception e) { - Log_OC.e(TAG, "Exception while copying foreign file " + expectedPath, e); - mForgottenLocalFiles.put(file.getRemotePath(), storagePath); - file.setStoragePath(null); - - } finally { - try { - if (in != null) in.close(); - } catch (Exception e) { - Log_OC.d(TAG, "Weird exception while closing input stream for " + storagePath + " (ignoring)", e); - } - try { - if (out != null) out.close(); - } catch (Exception e) { - Log_OC.d(TAG, "Weird exception while closing output stream for " + expectedPath + " (ignoring)", e); - } - } - } - } - } - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/UpdateOCVersionOperation.java b/src/de/mobilcom/debitel/cloud/android/operations/UpdateOCVersionOperation.java deleted file mode 100644 index fdf7fcab..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/UpdateOCVersionOperation.java +++ /dev/null @@ -1,108 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.operations; - -import org.apache.commons.httpclient.HttpStatus; -import org.apache.commons.httpclient.methods.GetMethod; -import org.json.JSONException; -import org.json.JSONObject; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.content.Context; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.authentication.AccountAuthenticator; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult.ResultCode; -import de.mobilcom.debitel.cloud.android.utils.OwnCloudVersion; - -import eu.alefzero.webdav.WebdavClient; - -/** - * Remote operation that checks the version of an ownCloud server and stores it locally - * - * @author David A. Velasco - */ -public class UpdateOCVersionOperation extends RemoteOperation { - - private static final String TAG = UpdateOCVersionOperation.class.getSimpleName(); - - private Account mAccount; - private Context mContext; - - - public UpdateOCVersionOperation(Account account, Context context) { - mAccount = account; - mContext = context; - } - - - @Override - protected RemoteOperationResult run(WebdavClient client) { - AccountManager accountMngr = AccountManager.get(mContext); - String statUrl = accountMngr.getUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL); - statUrl += AccountUtils.STATUS_PATH; - RemoteOperationResult result = null; - GetMethod get = null; - try { - get = new GetMethod(statUrl); - int status = client.executeMethod(get); - if (status != HttpStatus.SC_OK) { - client.exhaustResponse(get.getResponseBodyAsStream()); - result = new RemoteOperationResult(false, status, get.getResponseHeaders()); - - } else { - String response = get.getResponseBodyAsString(); - if (response != null) { - JSONObject json = new JSONObject(response); - if (json != null && json.getString("version") != null) { - OwnCloudVersion ocver = new OwnCloudVersion(json.getString("version")); - if (ocver.isVersionValid()) { - accountMngr.setUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION, ocver.toString()); - Log_OC.d(TAG, "Got new OC version " + ocver.toString()); - result = new RemoteOperationResult(ResultCode.OK); - - } else { - Log_OC.w(TAG, "Invalid version number received from server: " + json.getString("version")); - result = new RemoteOperationResult(RemoteOperationResult.ResultCode.BAD_OC_VERSION); - } - } - } - if (result == null) { - result = new RemoteOperationResult(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED); - } - } - Log_OC.i(TAG, "Check for update of ownCloud server version at " + client.getBaseUri() + ": " + result.getLogMessage()); - - } catch (JSONException e) { - result = new RemoteOperationResult(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED); - Log_OC.e(TAG, "Check for update of ownCloud server version at " + client.getBaseUri() + ": " + result.getLogMessage(), e); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Check for update of ownCloud server version at " + client.getBaseUri() + ": " + result.getLogMessage(), e); - - } finally { - if (get != null) - get.releaseConnection(); - } - return result; - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/operations/UploadFileOperation.java b/src/de/mobilcom/debitel/cloud/android/operations/UploadFileOperation.java deleted file mode 100644 index 5a5a1e48..00000000 --- a/src/de/mobilcom/debitel/cloud/android/operations/UploadFileOperation.java +++ /dev/null @@ -1,428 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.operations; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.apache.commons.httpclient.HttpException; -import org.apache.commons.httpclient.methods.PutMethod; -import org.apache.commons.httpclient.methods.RequestEntity; -import org.apache.http.HttpStatus; - -import android.accounts.Account; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.files.services.FileUploader; -import de.mobilcom.debitel.cloud.android.network.ProgressiveDataTransferer; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult.ResultCode; -import de.mobilcom.debitel.cloud.android.utils.FileStorageUtils; - -import eu.alefzero.webdav.FileRequestEntity; -import eu.alefzero.webdav.OnDatatransferProgressListener; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; - -/** - * Remote operation performing the upload of a file to an ownCloud server - * - * @author David A. Velasco - */ -public class UploadFileOperation extends RemoteOperation { - - private static final String TAG = UploadFileOperation.class.getSimpleName(); - - private Account mAccount; - private OCFile mFile; - private OCFile mOldFile; - private String mRemotePath = null; - private boolean mIsInstant = false; - private boolean mRemoteFolderToBeCreated = false; - private boolean mForceOverwrite = false; - private int mLocalBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY; - private boolean mWasRenamed = false; - private String mOriginalFileName = null; - private String mOriginalStoragePath = null; - PutMethod mPutMethod = null; - private Set mDataTransferListeners = new HashSet(); - private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false); - - protected RequestEntity mEntity = null; - - - public UploadFileOperation( Account account, - OCFile file, - boolean isInstant, - boolean forceOverwrite, - int localBehaviour) { - if (account == null) - throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation creation"); - if (file == null) - throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation"); - if (file.getStoragePath() == null || file.getStoragePath().length() <= 0 - || !(new File(file.getStoragePath()).exists())) { - throw new IllegalArgumentException( - "Illegal file in UploadFileOperation; storage path invalid or file not found: " - + file.getStoragePath()); - } - - mAccount = account; - mFile = file; - mRemotePath = file.getRemotePath(); - mIsInstant = isInstant; - mForceOverwrite = forceOverwrite; - mLocalBehaviour = localBehaviour; - mOriginalStoragePath = mFile.getStoragePath(); - mOriginalFileName = mFile.getFileName(); - } - - public Account getAccount() { - return mAccount; - } - - public String getFileName() { - return mOriginalFileName; - } - - public OCFile getFile() { - return mFile; - } - - public OCFile getOldFile() { - return mOldFile; - } - - public String getOriginalStoragePath() { - return mOriginalStoragePath; - } - - public String getStoragePath() { - return mFile.getStoragePath(); - } - - public String getRemotePath() { - return mFile.getRemotePath(); - } - - public String getMimeType() { - return mFile.getMimetype(); - } - - public boolean isInstant() { - return mIsInstant; - } - - public boolean isRemoteFolderToBeCreated() { - return mRemoteFolderToBeCreated; - } - - public void setRemoteFolderToBeCreated() { - mRemoteFolderToBeCreated = true; - } - - public boolean getForceOverwrite() { - return mForceOverwrite; - } - - public boolean wasRenamed() { - return mWasRenamed; - } - - public Set getDataTransferListeners() { - return mDataTransferListeners; - } - - public void addDatatransferProgressListener (OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.add(listener); - } - if (mEntity != null) { - ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListener(listener); - } - } - - public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.remove(listener); - } - if (mEntity != null) { - ((ProgressiveDataTransferer)mEntity).removeDatatransferProgressListener(listener); - } - } - - @Override - protected RemoteOperationResult run(WebdavClient client) { - RemoteOperationResult result = null; - boolean localCopyPassed = false, nameCheckPassed = false; - File temporalFile = null, originalFile = new File(mOriginalStoragePath), expectedFile = null; - try { - // / rename the file to upload, if necessary - if (!mForceOverwrite) { - String remotePath = getAvailableRemotePath(client, mRemotePath); - mWasRenamed = !remotePath.equals(mRemotePath); - if (mWasRenamed) { - createNewOCFile(remotePath); - } - } - nameCheckPassed = true; - - String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); // / - // not - // before - // getAvailableRemotePath() - // !!! - expectedFile = new File(expectedPath); - - // / 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) { - - if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) { - result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL); - return result; // error condition when the file should be - // copied - - } else { - String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath(); - mFile.setStoragePath(temporalPath); - temporalFile = new File(temporalPath); - if (!mOriginalStoragePath.equals(temporalPath)) { // preventing - // weird - // but - // possible - // situation - InputStream in = null; - OutputStream out = null; - try { - File temporalParent = temporalFile.getParentFile(); - temporalParent.mkdirs(); - if (!temporalParent.isDirectory()) { - 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"); - } - in = new FileInputStream(originalFile); - out = new FileOutputStream(temporalFile); - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - } - - } catch (Exception e) { - result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED); - return result; - - } finally { - try { - if (in != null) - in.close(); - } catch (Exception e) { - Log_OC.d(TAG, "Weird exception while closing input stream for " + mOriginalStoragePath + " (ignoring)", e); - } - try { - if (out != null) - out.close(); - } catch (Exception e) { - Log_OC.d(TAG, "Weird exception while closing output stream for " + expectedPath + " (ignoring)", e); - } - } - } - } - } - localCopyPassed = true; - - // / perform the upload - synchronized (mCancellationRequested) { - if (mCancellationRequested.get()) { - throw new OperationCancelledException(); - } else { - mPutMethod = new PutMethod(client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath())); - } - } - int status = uploadFile(client); - - // / move local temporal file or original file to its corresponding - // location in the ownCloud local folder - if (isSuccess(status)) { - if (mLocalBehaviour == FileUploader.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; - } - } - } - } - - result = new RemoteOperationResult(isSuccess(status), status, (mPutMethod != null ? mPutMethod.getResponseHeaders() : null)); - - } catch (Exception e) { - // TODO something cleaner with cancellations - if (mCancellationRequested.get()) { - result = new RemoteOperationResult(new OperationCancelledException()); - } else { - result = new RemoteOperationResult(e); - } - - } finally { - if (temporalFile != null && !originalFile.equals(temporalFile)) { - temporalFile.delete(); - } - if (result.isSuccess()) { - Log_OC.i(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage()); - } else { - if (result.getException() != null) { - String complement = ""; - if (!nameCheckPassed) { - complement = " (while checking file existence in server)"; - } else if (!localCopyPassed) { - complement = " (while copying local file to " + FileStorageUtils.getSavePath(mAccount.name) - + ")"; - } - 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()); - } - } - } - - return result; - } - - private void createNewOCFile(String newRemotePath) { - // a new OCFile instance must be created for a new remote path - OCFile newFile = new OCFile(newRemotePath); - newFile.setCreationTimestamp(mFile.getCreationTimestamp()); - newFile.setFileLength(mFile.getFileLength()); - newFile.setMimetype(mFile.getMimetype()); - newFile.setModificationTimestamp(mFile.getModificationTimestamp()); - newFile.setModificationTimestampAtLastSyncForData(mFile.getModificationTimestampAtLastSyncForData()); - // newFile.setEtag(mFile.getEtag()) - newFile.setKeepInSync(mFile.keepInSync()); - newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties()); - newFile.setLastSyncDateForData(mFile.getLastSyncDateForData()); - newFile.setStoragePath(mFile.getStoragePath()); - newFile.setParentId(mFile.getParentId()); - mOldFile = mFile; - mFile = newFile; - } - - public boolean isSuccess(int status) { - return ((status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED || status == HttpStatus.SC_NO_CONTENT)); - } - - protected int uploadFile(WebdavClient client) throws HttpException, IOException, OperationCancelledException { - int status = -1; - try { - File f = new File(mFile.getStoragePath()); - mEntity = new FileRequestEntity(f, getMimeType()); - synchronized (mDataTransferListeners) { - ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListeners(mDataTransferListeners); - } - mPutMethod.setRequestEntity(mEntity); - status = client.executeMethod(mPutMethod); - client.exhaustResponse(mPutMethod.getResponseBodyAsStream()); - - } finally { - mPutMethod.releaseConnection(); // let the connection available for - // other methods - } - return status; - } - - /** - * Checks if remotePath does not exist in the server and returns it, or adds - * a suffix to it in order to avoid the server file is overwritten. - * - * @param string - * @return - */ - private String getAvailableRemotePath(WebdavClient wc, String remotePath) throws Exception { - boolean check = wc.existsFile(remotePath); - if (!check) { - return remotePath; - } - - int pos = remotePath.lastIndexOf("."); - String suffix = ""; - String extension = ""; - if (pos >= 0) { - extension = remotePath.substring(pos + 1); - remotePath = remotePath.substring(0, pos); - } - int count = 2; - do { - suffix = " (" + count + ")"; - if (pos >= 0) - check = wc.existsFile(remotePath + suffix + "." + extension); - else - check = wc.existsFile(remotePath + suffix); - count++; - } while (check); - - if (pos >= 0) { - return remotePath + suffix + "." + extension; - } else { - return remotePath + suffix; - } - } - - public void cancel() { - synchronized (mCancellationRequested) { - mCancellationRequested.set(true); - if (mPutMethod != null) - mPutMethod.abort(); - } - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/providers/FileContentProvider.java b/src/de/mobilcom/debitel/cloud/android/providers/FileContentProvider.java deleted file mode 100644 index af31e668..00000000 --- a/src/de/mobilcom/debitel/cloud/android/providers/FileContentProvider.java +++ /dev/null @@ -1,301 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.providers; - -import java.util.HashMap; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.db.ProviderMeta; -import de.mobilcom.debitel.cloud.android.db.ProviderMeta.ProviderTableMeta; - - -import android.content.ContentProvider; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteQueryBuilder; -import android.net.Uri; -import android.text.TextUtils; - -/** - * The ContentProvider for the ownCloud App. - * - * @author Bartek Przybylski - * - */ -public class FileContentProvider extends ContentProvider { - - private DataBaseHelper mDbHelper; - - private static HashMap mProjectionMap; - static { - mProjectionMap = new HashMap(); - mProjectionMap.put(ProviderTableMeta._ID, ProviderTableMeta._ID); - mProjectionMap.put(ProviderTableMeta.FILE_PARENT, - ProviderTableMeta.FILE_PARENT); - mProjectionMap.put(ProviderTableMeta.FILE_PATH, - ProviderTableMeta.FILE_PATH); - mProjectionMap.put(ProviderTableMeta.FILE_NAME, - ProviderTableMeta.FILE_NAME); - mProjectionMap.put(ProviderTableMeta.FILE_CREATION, - ProviderTableMeta.FILE_CREATION); - mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED, - ProviderTableMeta.FILE_MODIFIED); - mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, - ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA); - mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_LENGTH, - ProviderTableMeta.FILE_CONTENT_LENGTH); - mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_TYPE, - ProviderTableMeta.FILE_CONTENT_TYPE); - mProjectionMap.put(ProviderTableMeta.FILE_STORAGE_PATH, - ProviderTableMeta.FILE_STORAGE_PATH); - mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, - ProviderTableMeta.FILE_LAST_SYNC_DATE); - mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, - ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA); - mProjectionMap.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, - ProviderTableMeta.FILE_KEEP_IN_SYNC); - mProjectionMap.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, - ProviderTableMeta.FILE_ACCOUNT_OWNER); - } - - private static final int SINGLE_FILE = 1; - private static final int DIRECTORY = 2; - private static final int ROOT_DIRECTORY = 3; - - private UriMatcher mUriMatcher; -// static { -// mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); -// mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, null, ROOT_DIRECTORY); -// mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "file/", SINGLE_FILE); -// mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "file/#", SINGLE_FILE); -// mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "dir/#", DIRECTORY); -// } - - - @Override - public int delete(Uri uri, String where, String[] whereArgs) { - SQLiteDatabase db = mDbHelper.getWritableDatabase(); - int count = 0; - switch (mUriMatcher.match(uri)) { - case SINGLE_FILE: - count = db.delete(ProviderTableMeta.DB_NAME, - ProviderTableMeta._ID - + "=" - + uri.getPathSegments().get(1) - + (!TextUtils.isEmpty(where) ? " AND (" + where - + ")" : ""), whereArgs); - break; - case ROOT_DIRECTORY: - count = db.delete(ProviderTableMeta.DB_NAME, where, whereArgs); - break; - default: - throw new IllegalArgumentException("Unknown uri: " + uri.toString()); - } - getContext().getContentResolver().notifyChange(uri, null); - return count; - } - - @Override - public String getType(Uri uri) { - switch (mUriMatcher.match(uri)) { - case ROOT_DIRECTORY: - return ProviderTableMeta.CONTENT_TYPE; - case SINGLE_FILE: - return ProviderTableMeta.CONTENT_TYPE_ITEM; - default: - throw new IllegalArgumentException("Unknown Uri id." - + uri.toString()); - } - } - - @Override - public Uri insert(Uri uri, ContentValues values) { - if (mUriMatcher.match(uri) != SINGLE_FILE && - mUriMatcher.match(uri) != ROOT_DIRECTORY) { - - throw new IllegalArgumentException("Unknown uri id: " + uri); - } - - SQLiteDatabase db = mDbHelper.getWritableDatabase(); - long rowId = db.insert(ProviderTableMeta.DB_NAME, null, values); - if (rowId > 0) { - Uri insertedFileUri = ContentUris.withAppendedId( - ProviderTableMeta.CONTENT_URI_FILE, rowId); - getContext().getContentResolver().notifyChange(insertedFileUri, - null); - return insertedFileUri; - } - throw new SQLException("ERROR " + uri); - } - - @Override - public boolean onCreate() { - mDbHelper = new DataBaseHelper(getContext()); - - String authority = getContext().getResources().getString(R.string.authority); - mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - mUriMatcher.addURI(authority, null, ROOT_DIRECTORY); - mUriMatcher.addURI(authority, "file/", SINGLE_FILE); - mUriMatcher.addURI(authority, "file/#", SINGLE_FILE); - mUriMatcher.addURI(authority, "dir/#", DIRECTORY); - - return true; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, - String[] selectionArgs, String sortOrder) { - SQLiteQueryBuilder sqlQuery = new SQLiteQueryBuilder(); - - sqlQuery.setTables(ProviderTableMeta.DB_NAME); - sqlQuery.setProjectionMap(mProjectionMap); - - switch (mUriMatcher.match(uri)) { - case ROOT_DIRECTORY: - break; - case DIRECTORY: - sqlQuery.appendWhere(ProviderTableMeta.FILE_PARENT + "=" - + uri.getPathSegments().get(1)); - break; - case SINGLE_FILE: - if (uri.getPathSegments().size() > 1) { - sqlQuery.appendWhere(ProviderTableMeta._ID + "=" - + uri.getPathSegments().get(1)); - } - break; - default: - throw new IllegalArgumentException("Unknown uri id: " + uri); - } - - String order; - if (TextUtils.isEmpty(sortOrder)) { - order = ProviderTableMeta.DEFAULT_SORT_ORDER; - } else { - order = sortOrder; - } - - SQLiteDatabase db = mDbHelper.getReadableDatabase(); - // DB case_sensitive - db.execSQL("PRAGMA case_sensitive_like = true"); - Cursor c = sqlQuery.query(db, projection, selection, selectionArgs, - null, null, order); - - c.setNotificationUri(getContext().getContentResolver(), uri); - - return c; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, - String[] selectionArgs) { - return mDbHelper.getWritableDatabase().update( - ProviderTableMeta.DB_NAME, values, selection, selectionArgs); - } - - class DataBaseHelper extends SQLiteOpenHelper { - - public DataBaseHelper(Context context) { - super(context, ProviderMeta.DB_NAME, null, ProviderMeta.DB_VERSION); - - } - - @Override - public void onCreate(SQLiteDatabase db) { - // files table - Log_OC.i("SQL", "Entering in onCreate"); - db.execSQL("CREATE TABLE " + ProviderTableMeta.DB_NAME + "(" - + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, " - + ProviderTableMeta.FILE_NAME + " TEXT, " - + ProviderTableMeta.FILE_PATH + " TEXT, " - + ProviderTableMeta.FILE_PARENT + " INTEGER, " - + ProviderTableMeta.FILE_CREATION + " INTEGER, " - + ProviderTableMeta.FILE_MODIFIED + " INTEGER, " - + ProviderTableMeta.FILE_CONTENT_TYPE + " TEXT, " - + ProviderTableMeta.FILE_CONTENT_LENGTH + " INTEGER, " - + ProviderTableMeta.FILE_STORAGE_PATH + " TEXT, " - + ProviderTableMeta.FILE_ACCOUNT_OWNER + " TEXT, " - + ProviderTableMeta.FILE_LAST_SYNC_DATE + " INTEGER, " - + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER, " - + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER, " - + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER );" - ); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - Log_OC.i("SQL", "Entering in onUpgrade"); - boolean upgraded = false; - if (oldVersion == 1 && newVersion >= 2) { - Log_OC.i("SQL", "Entering in the #1 ADD in onUpgrade"); - db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME + - " ADD COLUMN " + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER " + - " DEFAULT 0"); - upgraded = true; - } - if (oldVersion < 3 && newVersion >= 3) { - Log_OC.i("SQL", "Entering in the #2 ADD in onUpgrade"); - db.beginTransaction(); - try { - db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME + - " ADD COLUMN " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER " + - " DEFAULT 0"); - - // assume there are not local changes pending to upload - db.execSQL("UPDATE " + ProviderTableMeta.DB_NAME + - " SET " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " = " + System.currentTimeMillis() + - " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL"); - - upgraded = true; - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - if (oldVersion < 4 && newVersion >= 4) { - Log_OC.i("SQL", "Entering in the #3 ADD in onUpgrade"); - db.beginTransaction(); - try { - db .execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME + - " ADD COLUMN " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER " + - " DEFAULT 0"); - - db.execSQL("UPDATE " + ProviderTableMeta.DB_NAME + - " SET " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " = " + ProviderTableMeta.FILE_MODIFIED + - " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL"); - - upgraded = true; - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - if (!upgraded) - Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); - } - - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java b/src/de/mobilcom/debitel/cloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java deleted file mode 100644 index 1d4d4fbb..00000000 --- a/src/de/mobilcom/debitel/cloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java +++ /dev/null @@ -1,153 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.syncadapter; - -import java.io.IOException; -import java.util.Date; - -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.conn.ConnectionKeepAliveStrategy; -import org.apache.http.protocol.HttpContext; - -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils.AccountNotFoundException; -import de.mobilcom.debitel.cloud.android.datamodel.DataStorageManager; -import de.mobilcom.debitel.cloud.android.network.OwnCloudClientUtils; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AuthenticatorException; -import android.accounts.OperationCanceledException; -import android.content.AbstractThreadedSyncAdapter; -import android.content.ContentProviderClient; -import android.content.Context; -import eu.alefzero.webdav.WebdavClient; - -/** - * Base SyncAdapter for OwnCloud Designed to be subclassed for the concrete - * SyncAdapter, like ConcatsSync, CalendarSync, FileSync etc.. - * - * @author sassman - * - */ -public abstract class AbstractOwnCloudSyncAdapter extends - AbstractThreadedSyncAdapter { - - private AccountManager accountManager; - private Account account; - private ContentProviderClient contentProvider; - private Date lastUpdated; - private DataStorageManager mStoreManager; - - private WebdavClient mClient = null; - - public AbstractOwnCloudSyncAdapter(Context context, boolean autoInitialize) { - super(context, autoInitialize); - this.setAccountManager(AccountManager.get(context)); - } - - public AccountManager getAccountManager() { - return accountManager; - } - - public void setAccountManager(AccountManager accountManager) { - this.accountManager = accountManager; - } - - public Account getAccount() { - return account; - } - - public void setAccount(Account account) { - this.account = account; - } - - public ContentProviderClient getContentProvider() { - return contentProvider; - } - - public void setContentProvider(ContentProviderClient contentProvider) { - this.contentProvider = contentProvider; - } - - public Date getLastUpdated() { - return lastUpdated; - } - - public void setLastUpdated(Date lastUpdated) { - this.lastUpdated = lastUpdated; - } - - public void setStorageManager(DataStorageManager storage_manager) { - mStoreManager = storage_manager; - } - - public DataStorageManager getStorageManager() { - return mStoreManager; - } - - protected ConnectionKeepAliveStrategy getKeepAliveStrategy() { - return new ConnectionKeepAliveStrategy() { - public long getKeepAliveDuration(HttpResponse response, - HttpContext context) { - // Change keep alive straategy basing on response: ie - // forbidden/not found/etc - // should have keep alive 0 - // default return: 5s - int statusCode = response.getStatusLine().getStatusCode(); - - // HTTP 400, 500 Errors as well as HTTP 118 - Connection timed - // out - if ((statusCode >= 400 && statusCode <= 418) - || (statusCode >= 421 && statusCode <= 426) - || (statusCode >= 500 && statusCode <= 510) - || statusCode == 118) { - return 0; - } - - return 5 * 1000; - } - }; - } - - protected HttpResponse fireRawRequest(HttpRequest query) - throws ClientProtocolException, OperationCanceledException, - AuthenticatorException, IOException { - /* - * BasicHttpContext httpContext = new BasicHttpContext(); BasicScheme - * basicAuth = new BasicScheme(); - * httpContext.setAttribute("preemptive-auth", basicAuth); - * - * HttpResponse response = getClient().execute(mHost, query, - * httpContext); - */ - return null; - } - - protected void initClientForCurrentAccount() throws OperationCanceledException, AuthenticatorException, IOException, AccountNotFoundException { - AccountUtils.constructFullURLForAccount(getContext(), account); - mClient = OwnCloudClientUtils.createOwnCloudClient(account, getContext()); - } - - protected WebdavClient getClient() { - return mClient; - } -} \ No newline at end of file diff --git a/src/de/mobilcom/debitel/cloud/android/syncadapter/ContactSyncAdapter.java b/src/de/mobilcom/debitel/cloud/android/syncadapter/ContactSyncAdapter.java deleted file mode 100644 index ff2e84ad..00000000 --- a/src/de/mobilcom/debitel/cloud/android/syncadapter/ContactSyncAdapter.java +++ /dev/null @@ -1,122 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.syncadapter; - -import java.io.FileInputStream; -import java.io.IOException; - -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.ByteArrayEntity; - -import de.mobilcom.debitel.cloud.android.authentication.AccountAuthenticator; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AuthenticatorException; -import android.accounts.OperationCanceledException; -import android.content.ContentProviderClient; -import android.content.Context; -import android.content.SyncResult; -import android.content.res.AssetFileDescriptor; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.provider.ContactsContract; - -public class ContactSyncAdapter extends AbstractOwnCloudSyncAdapter { - private String mAddrBookUri; - - public ContactSyncAdapter(Context context, boolean autoInitialize) { - super(context, autoInitialize); - mAddrBookUri = null; - } - - @Override - public void onPerformSync(Account account, Bundle extras, String authority, - ContentProviderClient provider, SyncResult syncResult) { - setAccount(account); - setContentProvider(provider); - Cursor c = getLocalContacts(false); - if (c.moveToFirst()) { - do { - String lookup = c.getString(c - .getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)); - String a = getAddressBookUri(); - String uri = a + lookup + ".vcf"; - FileInputStream f; - try { - f = getContactVcard(lookup); - HttpPut query = new HttpPut(uri); - byte[] b = new byte[f.available()]; - f.read(b); - query.setEntity(new ByteArrayEntity(b)); - fireRawRequest(query); - } catch (IOException e) { - e.printStackTrace(); - return; - } catch (OperationCanceledException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (AuthenticatorException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } while (c.moveToNext()); - // } while (c.moveToNext()); - } - - } - - private String getAddressBookUri() { - if (mAddrBookUri != null) - return mAddrBookUri; - - AccountManager am = getAccountManager(); - @SuppressWarnings("deprecation") - String uri = am.getUserData(getAccount(), - AccountAuthenticator.KEY_OC_URL).replace( - AccountUtils.WEBDAV_PATH_2_0, AccountUtils.CARDDAV_PATH_2_0); - uri += "/addressbooks/" - + getAccount().name.substring(0, - getAccount().name.lastIndexOf('@')) + "/default/"; - mAddrBookUri = uri; - return uri; - } - - private FileInputStream getContactVcard(String lookupKey) - throws IOException { - Uri uri = Uri.withAppendedPath( - ContactsContract.Contacts.CONTENT_VCARD_URI, lookupKey); - AssetFileDescriptor fd = getContext().getContentResolver() - .openAssetFileDescriptor(uri, "r"); - return fd.createInputStream(); - } - - private Cursor getLocalContacts(boolean include_hidden_contacts) { - return getContext().getContentResolver().query( - ContactsContract.Contacts.CONTENT_URI, - new String[] { ContactsContract.Contacts._ID, - ContactsContract.Contacts.LOOKUP_KEY }, - ContactsContract.Contacts.IN_VISIBLE_GROUP + " = ?", - new String[] { (include_hidden_contacts ? "0" : "1") }, - ContactsContract.Contacts._ID + " DESC"); - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/syncadapter/ContactSyncService.java b/src/de/mobilcom/debitel/cloud/android/syncadapter/ContactSyncService.java deleted file mode 100644 index 9d15ca5b..00000000 --- a/src/de/mobilcom/debitel/cloud/android/syncadapter/ContactSyncService.java +++ /dev/null @@ -1,44 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.syncadapter; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; - -public class ContactSyncService extends Service { - private static final Object syncAdapterLock = new Object(); - private static AbstractOwnCloudSyncAdapter mSyncAdapter = null; - - @Override - public void onCreate() { - synchronized (syncAdapterLock) { - if (mSyncAdapter == null) { - mSyncAdapter = new ContactSyncAdapter(getApplicationContext(), - true); - } - } - } - - @Override - public IBinder onBind(Intent arg0) { - return mSyncAdapter.getSyncAdapterBinder(); - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/syncadapter/FileSyncAdapter.java b/src/de/mobilcom/debitel/cloud/android/syncadapter/FileSyncAdapter.java deleted file mode 100644 index e073971e..00000000 --- a/src/de/mobilcom/debitel/cloud/android/syncadapter/FileSyncAdapter.java +++ /dev/null @@ -1,409 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.syncadapter; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.jackrabbit.webdav.DavException; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.authentication.AuthenticatorActivity; -import de.mobilcom.debitel.cloud.android.datamodel.DataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.FileDataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult; -import de.mobilcom.debitel.cloud.android.operations.SynchronizeFolderOperation; -import de.mobilcom.debitel.cloud.android.operations.UpdateOCVersionOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult.ResultCode; -import de.mobilcom.debitel.cloud.android.ui.activity.ErrorsWhileCopyingHandlerActivity; - -import android.accounts.Account; -import android.accounts.AccountsException; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.ContentProviderClient; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.SyncResult; -import android.os.Bundle; - -/** - * SyncAdapter implementation for syncing sample SyncAdapter contacts to the - * platform ContactOperations provider. - * - * @author Bartek Przybylski - * @author David A. Velasco - */ -public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { - - private final static String TAG = "FileSyncAdapter"; - - /** - * Maximum number of failed folder synchronizations that are supported before finishing the synchronization operation - */ - private static final int MAX_FAILED_RESULTS = 3; - - private long mCurrentSyncTime; - private boolean mCancellation; - private boolean mIsManualSync; - private int mFailedResultsCounter; - private RemoteOperationResult mLastFailedResult; - private SyncResult mSyncResult; - private int mConflictsFound; - private int mFailsInFavouritesFound; - private Map mForgottenLocalFiles; - - - public FileSyncAdapter(Context context, boolean autoInitialize) { - super(context, autoInitialize); - } - - /** - * {@inheritDoc} - */ - @Override - public synchronized void onPerformSync(Account account, Bundle extras, - String authority, ContentProviderClient provider, - SyncResult syncResult) { - - mCancellation = false; - mIsManualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); - mFailedResultsCounter = 0; - mLastFailedResult = null; - mConflictsFound = 0; - mFailsInFavouritesFound = 0; - mForgottenLocalFiles = new HashMap(); - mSyncResult = syncResult; - mSyncResult.fullSyncRequested = false; - mSyncResult.delayUntil = 60*60*24; // sync after 24h - - this.setAccount(account); - this.setContentProvider(provider); - this.setStorageManager(new FileDataStorageManager(account, getContentProvider())); - try { - this.initClientForCurrentAccount(); - } catch (IOException e) { - /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again - mSyncResult.tooManyRetries = true; - notifyFailedSynchronization(); - return; - } catch (AccountsException e) { - /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again - mSyncResult.tooManyRetries = true; - notifyFailedSynchronization(); - return; - } - - Log_OC.d(TAG, "Synchronization of ownCloud account " + account.name + " starting"); - sendStickyBroadcast(true, null, null); // message to signal the start of the synchronization to the UI - - try { - updateOCVersion(); - mCurrentSyncTime = System.currentTimeMillis(); - if (!mCancellation) { - fetchData(OCFile.PATH_SEPARATOR, DataStorageManager.ROOT_PARENT_ID); - - } else { - Log_OC.d(TAG, "Leaving synchronization before any remote request due to cancellation was requested"); - } - - - } finally { - // it's important making this although very unexpected errors occur; that's the reason for the finally - - if (mFailedResultsCounter > 0 && mIsManualSync) { - /// don't let the system synchronization manager retries MANUAL synchronizations - // (be careful: "MANUAL" currently includes the synchronization requested when a new account is created and when the user changes the current account) - mSyncResult.tooManyRetries = true; - - /// notify the user about the failure of MANUAL synchronization - notifyFailedSynchronization(); - - } - if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) { - notifyFailsInFavourites(); - } - if (mForgottenLocalFiles.size() > 0) { - notifyForgottenLocalFiles(); - - } - sendStickyBroadcast(false, null, mLastFailedResult); // message to signal the end to the UI - } - - } - - /** - * Called by system SyncManager when a synchronization is required to be cancelled. - * - * Sets the mCancellation flag to 'true'. THe synchronization will be stopped when before a new folder is fetched. Data of the last folder - * fetched will be still saved in the database. See onPerformSync implementation. - */ - @Override - public void onSyncCanceled() { - Log_OC.d(TAG, "Synchronization of " + getAccount().name + " has been requested to cancel"); - mCancellation = true; - super.onSyncCanceled(); - } - - - /** - * Updates the locally stored version value of the ownCloud server - */ - private void updateOCVersion() { - UpdateOCVersionOperation update = new UpdateOCVersionOperation(getAccount(), getContext()); - RemoteOperationResult result = update.execute(getClient()); - if (!result.isSuccess()) { - mLastFailedResult = result; - } - } - - - /** - * Synchronize the properties of files and folders contained in a remote folder given by remotePath. - * - * @param remotePath Remote path to the folder to synchronize. - * @param parentId Database Id of the folder to synchronize. - */ - private void fetchData(String remotePath, long parentId) { - - if (mFailedResultsCounter > MAX_FAILED_RESULTS || isFinisher(mLastFailedResult)) - return; - - // perform folder synchronization - SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation( remotePath, - mCurrentSyncTime, - parentId, - getStorageManager(), - getAccount(), - getContext() - ); - RemoteOperationResult result = synchFolderOp.execute(getClient()); - - - // synchronized folder -> notice to UI - ALWAYS, although !result.isSuccess - sendStickyBroadcast(true, remotePath, null); - - if (result.isSuccess() || result.getCode() == ResultCode.SYNC_CONFLICT) { - - if (result.getCode() == ResultCode.SYNC_CONFLICT) { - mConflictsFound += synchFolderOp.getConflictsFound(); - mFailsInFavouritesFound += synchFolderOp.getFailsInFavouritesFound(); - } - if (synchFolderOp.getForgottenLocalFiles().size() > 0) { - mForgottenLocalFiles.putAll(synchFolderOp.getForgottenLocalFiles()); - } - // synchronize children folders - List children = synchFolderOp.getChildren(); - fetchChildren(children); // beware of the 'hidden' recursion here! - - sendStickyBroadcast(true, remotePath, null); - - } else { - if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED || - // (result.isTemporalRedirection() && result.isIdPRedirection() && - ( result.isIdPRedirection() && - MainApp.getAuthTokenTypeSamlSessionCookie().equals(getClient().getAuthTokenType()))) { - mSyncResult.stats.numAuthExceptions++; - - } else if (result.getException() instanceof DavException) { - mSyncResult.stats.numParseExceptions++; - - } else if (result.getException() instanceof IOException) { - mSyncResult.stats.numIoExceptions++; - } - mFailedResultsCounter++; - mLastFailedResult = result; - } - - } - - /** - * Checks if a failed result should terminate the synchronization process immediately, according to - * OUR OWN POLICY - * - * @param failedResult Remote operation result to check. - * @return 'True' if the result should immediately finish the synchronization - */ - private boolean isFinisher(RemoteOperationResult failedResult) { - if (failedResult != null) { - RemoteOperationResult.ResultCode code = failedResult.getCode(); - return (code.equals(RemoteOperationResult.ResultCode.SSL_ERROR) || - code.equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) || - code.equals(RemoteOperationResult.ResultCode.BAD_OC_VERSION) || - code.equals(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED)); - } - return false; - } - - /** - * Synchronize data of folders in the list of received files - * - * @param files Files to recursively fetch - */ - private void fetchChildren(List files) { - int i; - for (i=0; i < files.size() && !mCancellation; i++) { - OCFile newFile = files.get(i); - if (newFile.isDirectory()) { - fetchData(newFile.getRemotePath(), newFile.getFileId()); - - // Update folder size on DB - getStorageManager().calculateFolderSize(newFile.getFileId()); - } - } - - if (mCancellation && i 0) { - Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_fail_in_favourites_ticker), System.currentTimeMillis()); - notification.flags |= Notification.FLAG_AUTO_CANCEL; - // TODO put something smart in the contentIntent below - notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); - notification.setLatestEventInfo(getContext().getApplicationContext(), - getContext().getString(R.string.sync_fail_in_favourites_ticker), - String.format(getContext().getString(R.string.sync_fail_in_favourites_content), mFailedResultsCounter + mConflictsFound, mConflictsFound), - notification.contentIntent); - ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_in_favourites_ticker, notification); - - } else { - Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_conflicts_in_favourites_ticker), System.currentTimeMillis()); - notification.flags |= Notification.FLAG_AUTO_CANCEL; - // TODO put something smart in the contentIntent below - notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); - notification.setLatestEventInfo(getContext().getApplicationContext(), - getContext().getString(R.string.sync_conflicts_in_favourites_ticker), - String.format(getContext().getString(R.string.sync_conflicts_in_favourites_content), mConflictsFound), - notification.contentIntent); - ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_conflicts_in_favourites_ticker, notification); - } - } - - /** - * Notifies the user about local copies of files out of the ownCloud local directory that were 'forgotten' because - * copying them inside the ownCloud local directory was not possible. - * - * We don't want links to files out of the ownCloud local directory (foreign files) anymore. It's easy to have - * synchronization problems if a local file is linked to more than one remote file. - * - * We won't consider a synchronization as failed when foreign files can not be copied to the ownCloud local directory. - */ - private void notifyForgottenLocalFiles() { - Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_foreign_files_forgotten_ticker), System.currentTimeMillis()); - notification.flags |= Notification.FLAG_AUTO_CANCEL; - - /// includes a pending intent in the notification showing a more detailed explanation - Intent explanationIntent = new Intent(getContext(), ErrorsWhileCopyingHandlerActivity.class); - explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_ACCOUNT, getAccount()); - ArrayList remotePaths = new ArrayList(); - ArrayList localPaths = new ArrayList(); - remotePaths.addAll(mForgottenLocalFiles.keySet()); - localPaths.addAll(mForgottenLocalFiles.values()); - explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_LOCAL_PATHS, localPaths); - explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_REMOTE_PATHS, remotePaths); - explanationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - - notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), explanationIntent, 0); - notification.setLatestEventInfo(getContext().getApplicationContext(), - getContext().getString(R.string.sync_foreign_files_forgotten_ticker), - String.format(getContext().getString(R.string.sync_foreign_files_forgotten_content), mForgottenLocalFiles.size(), getContext().getString(R.string.app_name)), - notification.contentIntent); - ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_foreign_files_forgotten_ticker, notification); - - } - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/syncadapter/FileSyncService.java b/src/de/mobilcom/debitel/cloud/android/syncadapter/FileSyncService.java deleted file mode 100644 index de0f4598..00000000 --- a/src/de/mobilcom/debitel/cloud/android/syncadapter/FileSyncService.java +++ /dev/null @@ -1,54 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.syncadapter; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; - -/** - * Background service for syncing files to our local Database - * - * @author Bartek Przybylski - * - */ -public class FileSyncService extends Service { - public static final String SYNC_MESSAGE = "ACCOUNT_SYNC"; - public static final String SYNC_FOLDER_REMOTE_PATH = "SYNC_FOLDER_REMOTE_PATH"; - public static final String IN_PROGRESS = "SYNC_IN_PROGRESS"; - public static final String ACCOUNT_NAME = "ACCOUNT_NAME"; - public static final String SYNC_RESULT = "SYNC_RESULT"; - - public String getSyncMessage(){ - return getClass().getName().toString() + SYNC_MESSAGE; - } - /* - * {@inheritDoc} - */ - @Override - public void onCreate() { - } - - /* - * {@inheritDoc} - */ - @Override - public IBinder onBind(Intent intent) { - return new FileSyncAdapter(getApplicationContext(), true).getSyncAdapterBinder(); - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/ActionItem.java b/src/de/mobilcom/debitel/cloud/android/ui/ActionItem.java deleted file mode 100644 index 6b907678..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/ActionItem.java +++ /dev/null @@ -1,61 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.ui; - -import android.graphics.drawable.Drawable; -import android.view.View.OnClickListener; - -/** - * Represents an Item on the ActionBar. - * - * @author Bartek Przybylski - * - */ -public class ActionItem { - private Drawable mIcon; - private String mTitle; - private OnClickListener mClickListener; - - public ActionItem() { - } - - public void setTitle(String title) { - mTitle = title; - } - - public String getTitle() { - return mTitle; - } - - public void setIcon(Drawable icon) { - mIcon = icon; - } - - public Drawable getIcon() { - return mIcon; - } - - public void setOnClickListener(OnClickListener listener) { - mClickListener = listener; - } - - public OnClickListener getOnClickListerner() { - return mClickListener; - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/CustomButton.java b/src/de/mobilcom/debitel/cloud/android/ui/CustomButton.java deleted file mode 100644 index 09b82157..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/CustomButton.java +++ /dev/null @@ -1,45 +0,0 @@ -package de.mobilcom.debitel.cloud.android.ui; - -import de.mobilcom.debitel.cloud.android.R; -import android.content.Context; -import android.util.AttributeSet; -import android.widget.Button; -/** - * @author masensio - * - * Button for customizing the button background - */ - -public class CustomButton extends Button { - - public CustomButton(Context context) { - super(context); - - boolean customButtons = getResources().getBoolean(R.bool.custom_buttons); - if (customButtons) - { - this.setBackgroundResource(R.drawable.btn_default); - } - } - - public CustomButton(Context context, AttributeSet attrs) { - super(context, attrs); - - boolean customButtons = getResources().getBoolean(R.bool.custom_buttons); - if (customButtons) - { - this.setBackgroundResource(R.drawable.btn_default); - } - } - - public CustomButton(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - boolean customButtons = getResources().getBoolean(R.bool.custom_buttons); - if (customButtons) - { - this.setBackgroundResource(R.drawable.btn_default); - } - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/CustomPopup.java b/src/de/mobilcom/debitel/cloud/android/ui/CustomPopup.java deleted file mode 100644 index 244a1935..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/CustomPopup.java +++ /dev/null @@ -1,154 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.ui; - -import android.content.Context; -import android.graphics.Rect; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.WindowManager; -import android.view.View.OnTouchListener; -import android.view.ViewGroup.LayoutParams; -import android.widget.PopupWindow; - -/** - * Represents a custom PopupWindows - * - * @author Lorensius. W. T - * - */ -public class CustomPopup { - protected final View mAnchor; - protected final PopupWindow mWindow; - private View root; - private Drawable background = null; - protected final WindowManager mWManager; - - public CustomPopup(View anchor) { - mAnchor = anchor; - mWindow = new PopupWindow(anchor.getContext()); - - mWindow.setTouchInterceptor(new OnTouchListener() { - - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { - CustomPopup.this.dismiss(); - return true; - } - return false; - } - }); - - mWManager = (WindowManager) anchor.getContext().getSystemService( - Context.WINDOW_SERVICE); - onCreate(); - } - - public void onCreate() { - } - - public void onShow() { - } - - public void preShow() { - if (root == null) { - throw new IllegalStateException( - "setContentView called with a view to display"); - } - - onShow(); - - if (background == null) { - mWindow.setBackgroundDrawable(new BitmapDrawable()); - } else { - mWindow.setBackgroundDrawable(background); - } - - mWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT); - mWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT); - mWindow.setTouchable(true); - mWindow.setFocusable(true); - mWindow.setOutsideTouchable(true); - - mWindow.setContentView(root); - } - - public void setBackgroundDrawable(Drawable background) { - this.background = background; - } - - public void setContentView(View root) { - this.root = root; - mWindow.setContentView(root); - } - - public void setContentView(int layoutResId) { - LayoutInflater inflater = (LayoutInflater) mAnchor.getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - setContentView(inflater.inflate(layoutResId, null)); - } - - public void showDropDown() { - showDropDown(0, 0); - } - - public void showDropDown(int x, int y) { - preShow(); - mWindow.setAnimationStyle(android.R.style.Animation_Dialog); - mWindow.showAsDropDown(mAnchor, x, y); - } - - public void showLikeQuickAction() { - showLikeQuickAction(0, 0); - } - - public void showLikeQuickAction(int x, int y) { - preShow(); - - mWindow.setAnimationStyle(android.R.style.Animation_Dialog); - int[] location = new int[2]; - mAnchor.getLocationOnScreen(location); - - Rect anchorRect = new Rect(location[0], location[1], location[0] - + mAnchor.getWidth(), location[1] + mAnchor.getHeight()); - - root.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT)); - root.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - - int rootW = root.getWidth(), rootH = root.getHeight(); - int screenW = mWManager.getDefaultDisplay().getWidth(); - - int xpos = ((screenW - rootW) / 2) + x; - int ypos = anchorRect.top - rootH + y; - - if (rootH > anchorRect.top) { - ypos = anchorRect.bottom + y; - } - mWindow.showAtLocation(mAnchor, Gravity.NO_GRAVITY, xpos, ypos); - } - - public void dismiss() { - mWindow.dismiss(); - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/ExtendedListView.java b/src/de/mobilcom/debitel/cloud/android/ui/ExtendedListView.java deleted file mode 100644 index 15a797fe..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/ExtendedListView.java +++ /dev/null @@ -1,73 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.ui; - -import android.content.Context; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.widget.ListView; - -/** - * ListView allowing to specify the position of an item that should be centered in the visible area, if possible. - * - * The cleanest way I found to overcome the problem due to getHeight() returns 0 until the view is really drawn. - * - * @author David A. Velasco - */ -public class ExtendedListView extends ListView { - - private int mPositionToSetAndCenter; - - public ExtendedListView(Context context) { - super(context); - } - - public ExtendedListView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public ExtendedListView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - /** - * {@inheritDoc} - * - * - */ - @Override - protected void onDraw (Canvas canvas) { - super.onDraw(canvas); - if (mPositionToSetAndCenter > 0) { - this.setSelectionFromTop(mPositionToSetAndCenter, getHeight() / 2); - mPositionToSetAndCenter = 0; - } - } - - /** - * Public method to set the position of the item that should be centered in the visible area of the view. - * - * The position is saved here and checked in onDraw(). - * - * @param position Position (in the list of items) of the item to center in the visible area. - */ - public void setAndCenterSelection(int position) { - mPositionToSetAndCenter = position; - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/QuickAction.java b/src/de/mobilcom/debitel/cloud/android/ui/QuickAction.java deleted file mode 100644 index 52408390..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/QuickAction.java +++ /dev/null @@ -1,305 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.ui; - -import android.content.Context; - -import android.graphics.Rect; -import android.graphics.drawable.Drawable; - -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.LinearLayout; -import android.widget.ScrollView; - -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup.LayoutParams; -import android.view.ViewGroup; - -import java.util.ArrayList; - -import de.mobilcom.debitel.cloud.android.R; - -/** - * Popup window, shows action list as icon and text like the one in Gallery3D - * app. - * - * @author Lorensius. W. T - */ -public class QuickAction extends CustomPopup { - private final View root; - private final ImageView mArrowUp; - private final ImageView mArrowDown; - private final LayoutInflater inflater; - private final Context context; - - protected static final int ANIM_GROW_FROM_LEFT = 1; - protected static final int ANIM_GROW_FROM_RIGHT = 2; - protected static final int ANIM_GROW_FROM_CENTER = 3; - protected static final int ANIM_REFLECT = 4; - protected static final int ANIM_AUTO = 5; - - private int animStyle; - private ViewGroup mTrack; - private ScrollView scroller; - private ArrayList actionList; - - /** - * Constructor - * - * @param anchor {@link View} on where the popup window should be displayed - */ - public QuickAction(View anchor) { - super(anchor); - - actionList = new ArrayList(); - context = anchor.getContext(); - inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - root = (ViewGroup) inflater.inflate(R.layout.popup, null); - - mArrowDown = (ImageView) root.findViewById(R.id.arrow_down); - mArrowUp = (ImageView) root.findViewById(R.id.arrow_up); - - setContentView(root); - - mTrack = (ViewGroup) root.findViewById(R.id.tracks); - scroller = (ScrollView) root.findViewById(R.id.scroller); - animStyle = ANIM_AUTO; - } - - /** - * Set animation style - * - * @param animStyle animation style, default is set to ANIM_AUTO - */ - public void setAnimStyle(int animStyle) { - this.animStyle = animStyle; - } - - /** - * Add action item - * - * @param action {@link ActionItem} object - */ - public void addActionItem(ActionItem action) { - actionList.add(action); - } - - /** - * Show popup window. Popup is automatically positioned, on top or bottom of - * anchor view. - * - */ - public void show() { - preShow(); - - int xPos, yPos; - - int[] location = new int[2]; - - mAnchor.getLocationOnScreen(location); - - Rect anchorRect = new Rect(location[0], location[1], location[0] - + mAnchor.getWidth(), location[1] + mAnchor.getHeight()); - - createActionList(); - - root.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT)); - root.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - - int rootHeight = root.getMeasuredHeight(); - int rootWidth = root.getMeasuredWidth(); - - int screenWidth = mWManager.getDefaultDisplay().getWidth(); - int screenHeight = mWManager.getDefaultDisplay().getHeight(); - - // automatically get X coord of popup (top left) - if ((anchorRect.left + rootWidth) > screenWidth) { - xPos = anchorRect.left - (rootWidth - mAnchor.getWidth()); - } else { - if (mAnchor.getWidth() > rootWidth) { - xPos = anchorRect.centerX() - (rootWidth / 2); - } else { - xPos = anchorRect.left; - } - } - - int dyTop = anchorRect.top; - int dyBottom = screenHeight - anchorRect.bottom; - - boolean onTop = (dyTop > dyBottom) ? true : false; - - if (onTop) { - if (rootHeight > dyTop) { - yPos = 15; - LayoutParams l = scroller.getLayoutParams(); - l.height = dyTop - mAnchor.getHeight(); - } else { - yPos = anchorRect.top - rootHeight; - } - } else { - yPos = anchorRect.bottom; - - if (rootHeight > dyBottom) { - LayoutParams l = scroller.getLayoutParams(); - l.height = dyBottom; - } - } - - showArrow(((onTop) ? R.id.arrow_down : R.id.arrow_up), - anchorRect.centerX() - xPos); - - setAnimationStyle(screenWidth, anchorRect.centerX(), onTop); - - mWindow.showAtLocation(mAnchor, Gravity.NO_GRAVITY, xPos, yPos); - } - - /** - * Set animation style - * - * @param screenWidth screen width - * @param requestedX distance from left edge - * @param onTop flag to indicate where the popup should be displayed. Set - * TRUE if displayed on top of anchor view and vice versa - */ - private void setAnimationStyle(int screenWidth, int requestedX, - boolean onTop) { - int arrowPos = requestedX - mArrowUp.getMeasuredWidth() / 2; - - switch (animStyle) { - case ANIM_GROW_FROM_LEFT: - mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Left - : R.style.Animations_PopDownMenu_Left); - break; - - case ANIM_GROW_FROM_RIGHT: - mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Right - : R.style.Animations_PopDownMenu_Right); - break; - - case ANIM_GROW_FROM_CENTER: - mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Center - : R.style.Animations_PopDownMenu_Center); - break; - - case ANIM_REFLECT: - mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Reflect - : R.style.Animations_PopDownMenu_Reflect); - break; - - case ANIM_AUTO: - if (arrowPos <= screenWidth / 4) { - mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Left - : R.style.Animations_PopDownMenu_Left); - } else if (arrowPos > screenWidth / 4 - && arrowPos < 3 * (screenWidth / 4)) { - mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Center - : R.style.Animations_PopDownMenu_Center); - } else { - mWindow.setAnimationStyle((onTop) ? R.style.Animations_PopUpMenu_Right - : R.style.Animations_PopDownMenu_Right); - } - - break; - } - } - - /** - * Create action list - */ - private void createActionList() { - View view; - String title; - Drawable icon; - OnClickListener listener; - - for (int i = 0; i < actionList.size(); i++) { - title = actionList.get(i).getTitle(); - icon = actionList.get(i).getIcon(); - listener = actionList.get(i).getOnClickListerner(); - - view = getActionItem(title, icon, listener); - - view.setFocusable(true); - view.setClickable(true); - - mTrack.addView(view); - } - } - - /** - * Get action item {@link View} - * - * @param title action item title - * @param icon {@link Drawable} action item icon - * @param listener {@link View.OnClickListener} action item listener - * @return action item {@link View} - */ - private View getActionItem(String title, Drawable icon, - OnClickListener listener) { - LinearLayout container = (LinearLayout) inflater.inflate( - R.layout.action_item, null); - - ImageView img = (ImageView) container.findViewById(R.id.icon); - TextView text = (TextView) container.findViewById(R.id.title); - - if (icon != null) { - img.setImageDrawable(icon); - } - - if (title != null) { - text.setText(title); - } - - if (listener != null) { - container.setOnClickListener(listener); - } - - return container; - } - - /** - * Show arrow - * - * @param whichArrow arrow type resource id - * @param requestedX distance from left screen - */ - private void showArrow(int whichArrow, int requestedX) { - final View showArrow = (whichArrow == R.id.arrow_up) ? mArrowUp - : mArrowDown; - final View hideArrow = (whichArrow == R.id.arrow_up) ? mArrowDown - : mArrowUp; - - final int arrowWidth = mArrowUp.getMeasuredWidth(); - - showArrow.setVisibility(View.VISIBLE); - - ViewGroup.MarginLayoutParams param = (ViewGroup.MarginLayoutParams) showArrow - .getLayoutParams(); - - param.leftMargin = requestedX - arrowWidth / 2; - - hideArrow.setVisibility(View.INVISIBLE); - } -} \ No newline at end of file diff --git a/src/de/mobilcom/debitel/cloud/android/ui/activity/AccountSelectActivity.java b/src/de/mobilcom/debitel/cloud/android/ui/activity/AccountSelectActivity.java deleted file mode 100644 index 852f8101..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/activity/AccountSelectActivity.java +++ /dev/null @@ -1,274 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.ui.activity; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AccountManagerCallback; -import android.accounts.AccountManagerFuture; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.view.ContextMenu; -import android.view.View; -import android.view.ViewGroup; -import android.view.ContextMenu.ContextMenuInfo; -import android.widget.AdapterView.AdapterContextMenuInfo; -import android.widget.CheckedTextView; -import android.widget.ListView; -import android.widget.SimpleAdapter; -import android.widget.TextView; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.SherlockListActivity; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -import com.actionbarsherlock.view.MenuItem; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.authentication.AccountAuthenticator; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; -import de.mobilcom.debitel.cloud.android.authentication.AuthenticatorActivity; - -public class AccountSelectActivity extends SherlockListActivity implements - AccountManagerCallback { - - private static final String TAG = "AccountSelectActivity"; - - private static final String PREVIOUS_ACCOUNT_KEY = "ACCOUNT"; - - private final Handler mHandler = new Handler(); - private Account mPreviousAccount = null; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (savedInstanceState != null) { - mPreviousAccount = savedInstanceState.getParcelable(PREVIOUS_ACCOUNT_KEY); - } else { - mPreviousAccount = AccountUtils.getCurrentOwnCloudAccount(this); - } - - ActionBar action_bar = getSupportActionBar(); - action_bar.setDisplayShowTitleEnabled(true); - action_bar.setDisplayHomeAsUpEnabled(false); - } - - @Override - protected void onResume() { - super.onResume(); - populateAccountList(); - } - - @Override - protected void onPause() { - super.onPause(); - if (this.isFinishing()) { - Account current = AccountUtils.getCurrentOwnCloudAccount(this); - if ((mPreviousAccount == null && current != null) || - (mPreviousAccount != null && !mPreviousAccount.equals(current))) { - /// the account set as default changed since this activity was created - - // trigger synchronization - ContentResolver.cancelSync(null, MainApp.getAuthTokenType()); - Bundle bundle = new Bundle(); - bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - ContentResolver.requestSync(AccountUtils.getCurrentOwnCloudAccount(this), MainApp.getAuthTokenType(), bundle); - - // restart the main activity - Intent i = new Intent(this, FileDisplayActivity.class); - i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(i); - } - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Show Create Account if Multiaccount is enabled - if (getResources().getBoolean(R.bool.multiaccount_support)) { - MenuInflater inflater = getSherlock().getMenuInflater(); - inflater.inflate(R.menu.account_picker, menu); - } - return true; - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { - getMenuInflater().inflate(R.menu.account_picker_long_click, menu); - super.onCreateContextMenu(menu, v, menuInfo); - } - - @Override - protected void onListItemClick(ListView l, View v, int position, long id) { - String accountName = ((TextView) v.findViewById(android.R.id.text1)) - .getText().toString(); - AccountUtils.setCurrentOwnCloudAccount(this, accountName); - finish(); // immediate exit - } - - @Override - public boolean onMenuItemSelected(int featureId, MenuItem item) { - if (item.getItemId() == R.id.createAccount) { - /*Intent intent = new Intent( - android.provider.Settings.ACTION_ADD_ACCOUNT); - intent.putExtra("authorities", - new String[] { MainApp.getAuthTokenType() }); - startActivity(intent);*/ - AccountManager am = AccountManager.get(getApplicationContext()); - am.addAccount(MainApp.getAccountType(), - null, - null, - null, - this, - null, - null); - return true; - } - return false; - } - - /** - * Called when the user clicked on an item into the context menu created at - * {@link #onCreateContextMenu(ContextMenu, View, ContextMenuInfo)} for every - * ownCloud {@link Account} , containing 'secondary actions' for them. - * - * {@inheritDoc}} - */ - @SuppressWarnings("unchecked") - @Override - public boolean onContextItemSelected(android.view.MenuItem item) { - AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); - int index = info.position; - HashMap map = null; - try { - map = (HashMap) getListAdapter().getItem(index); - } catch (ClassCastException e) { - Log_OC.wtf(TAG, "getitem(index) from list adapter did not return hashmap, bailing out"); - return false; - } - - String accountName = map.get("NAME"); - AccountManager am = (AccountManager) getSystemService(ACCOUNT_SERVICE); - Account accounts[] = am.getAccountsByType(MainApp.getAccountType()); - for (Account a : accounts) { - if (a.name.equals(accountName)) { - if (item.getItemId() == R.id.change_password) { - Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class); - updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, a); - updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_TOKEN); - startActivity(updateAccountCredentials); - - } else if (item.getItemId() == R.id.delete_account) { - am.removeAccount(a, this, mHandler); - } - } - } - - return true; - } - - private void populateAccountList() { - AccountManager am = (AccountManager) getSystemService(ACCOUNT_SERVICE); - Account accounts[] = am - .getAccountsByType(MainApp.getAccountType()); - if (am.getAccountsByType(MainApp.getAccountType()).length == 0) { - // Show create account screen if there isn't any account - am.addAccount(MainApp.getAccountType(), - null, - null, - null, - this, - null, - null); - } - else { - LinkedList> ll = new LinkedList>(); - for (Account a : accounts) { - HashMap h = new HashMap(); - h.put("NAME", a.name); - h.put("VER", - "ownCloud version: " - + am.getUserData(a, - AccountAuthenticator.KEY_OC_VERSION)); - ll.add(h); - } - - setListAdapter(new AccountCheckedSimpleAdepter(this, ll, - android.R.layout.simple_list_item_single_choice, - new String[] { "NAME" }, new int[] { android.R.id.text1 })); - registerForContextMenu(getListView()); - } - } - - @Override - public void run(AccountManagerFuture future) { - if (future.isDone()) { - Account a = AccountUtils.getCurrentOwnCloudAccount(this); - String accountName = ""; - if (a == null) { - Account[] accounts = AccountManager.get(this) - .getAccountsByType(MainApp.getAccountType()); - if (accounts.length != 0) - accountName = accounts[0].name; - AccountUtils.setCurrentOwnCloudAccount(this, accountName); - } - populateAccountList(); - } - } - - private class AccountCheckedSimpleAdepter extends SimpleAdapter { - private Account mCurrentAccount; - private List> mPrivateData; - - public AccountCheckedSimpleAdepter(Context context, - List> data, int resource, - String[] from, int[] to) { - super(context, data, resource, from, to); - mCurrentAccount = AccountUtils - .getCurrentOwnCloudAccount(AccountSelectActivity.this); - mPrivateData = data; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View v = super.getView(position, convertView, parent); - CheckedTextView ctv = (CheckedTextView) v - .findViewById(android.R.id.text1); - if (mPrivateData.get(position).get("NAME") - .equals(mCurrentAccount.name)) { - ctv.setChecked(true); - } - return v; - } - - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/activity/ConflictsResolveActivity.java b/src/de/mobilcom/debitel/cloud/android/ui/activity/ConflictsResolveActivity.java deleted file mode 100644 index e87febb3..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/activity/ConflictsResolveActivity.java +++ /dev/null @@ -1,103 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.ui.activity; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.datamodel.DataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.FileDataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.files.services.FileUploader; -import de.mobilcom.debitel.cloud.android.ui.dialog.ConflictsResolveDialog; -import de.mobilcom.debitel.cloud.android.ui.dialog.ConflictsResolveDialog.Decision; -import de.mobilcom.debitel.cloud.android.ui.dialog.ConflictsResolveDialog.OnConflictDecisionMadeListener; - -import android.content.Intent; -import android.os.Bundle; - -/** - * Wrapper activity which will be launched if keep-in-sync file will be modified by external - * application. - * - * @author Bartek Przybylski - * @author David A. Velasco - */ -public class ConflictsResolveActivity extends FileActivity implements OnConflictDecisionMadeListener { - - private String TAG = ConflictsResolveActivity.class.getSimpleName(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @Override - public void ConflictDecisionMade(Decision decision) { - Intent i = new Intent(getApplicationContext(), FileUploader.class); - - switch (decision) { - case CANCEL: - finish(); - return; - case OVERWRITE: - i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true); - break; - case KEEP_BOTH: - i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE); - break; - default: - 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); - - startService(i); - finish(); - } - - @Override - protected void onAccountSet(boolean stateWasRecovered) { - if (getAccount() != null) { - OCFile file = getFile(); - if (getFile() == null) { - Log_OC.e(TAG, "No conflictive file received"); - finish(); - } else { - /// Check whether the 'main' OCFile handled by the Activity is contained in the current Account - DataStorageManager storageManager = new FileDataStorageManager(getAccount(), getContentResolver()); - file = storageManager.getFileByPath(file.getRemotePath()); // file = null if not in the current Account - if (file != null) { - setFile(file); - ConflictsResolveDialog d = ConflictsResolveDialog.newInstance(file.getRemotePath(), this); - d.showDialog(this); - - } else { - // account was changed to a different one - just finish - finish(); - } - } - - } else { - Log_OC.wtf(TAG, "onAccountChanged was called with NULL account associated!"); - finish(); - } - - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java b/src/de/mobilcom/debitel/cloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java deleted file mode 100644 index 33ffc32f..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java +++ /dev/null @@ -1,277 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.activity; - -import java.io.File; -import java.util.ArrayList; - -import android.accounts.Account; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.support.v4.app.DialogFragment; -import android.text.method.ScrollingMovementMethod; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.Toast; - -import com.actionbarsherlock.app.SherlockFragmentActivity; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.datamodel.FileDataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.ui.CustomButton; -import de.mobilcom.debitel.cloud.android.ui.dialog.IndeterminateProgressDialog; -import de.mobilcom.debitel.cloud.android.utils.FileStorageUtils; - - -/** - * Activity reporting errors occurred when local files uploaded to an ownCloud account with an app in - * version under 1.3.16 where being copied to the ownCloud local folder. - * - * Allows the user move the files to the ownCloud local folder. let them unlinked to the remote - * files. - * - * Shown when the error notification summarizing the list of errors is clicked by the user. - * - * @author David A. Velasco - */ -public class ErrorsWhileCopyingHandlerActivity extends SherlockFragmentActivity implements OnClickListener { - - private static final String TAG = ErrorsWhileCopyingHandlerActivity.class.getSimpleName(); - - public static final String EXTRA_ACCOUNT = ErrorsWhileCopyingHandlerActivity.class.getCanonicalName() + ".EXTRA_ACCOUNT"; - public static final String EXTRA_LOCAL_PATHS = ErrorsWhileCopyingHandlerActivity.class.getCanonicalName() + ".EXTRA_LOCAL_PATHS"; - public static final String EXTRA_REMOTE_PATHS = ErrorsWhileCopyingHandlerActivity.class.getCanonicalName() + ".EXTRA_REMOTE_PATHS"; - - private static final String WAIT_DIALOG_TAG = "WAIT_DIALOG"; - - protected Account mAccount; - protected FileDataStorageManager mStorageManager; - protected ArrayList mLocalPaths; - protected ArrayList mRemotePaths; - protected ArrayAdapter mAdapter; - protected Handler mHandler; - private DialogFragment mCurrentDialog; - - /** - * {@link} - */ - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - /// read extra parameters in intent - Intent intent = getIntent(); - mAccount = intent.getParcelableExtra(EXTRA_ACCOUNT); - mRemotePaths = intent.getStringArrayListExtra(EXTRA_REMOTE_PATHS); - mLocalPaths = intent.getStringArrayListExtra(EXTRA_LOCAL_PATHS); - mStorageManager = new FileDataStorageManager(mAccount, getContentResolver()); - mHandler = new Handler(); - if (mCurrentDialog != null) { - mCurrentDialog.dismiss(); - mCurrentDialog = null; - } - - /// load generic layout - setContentView(R.layout.generic_explanation); - - /// customize text message - TextView textView = (TextView) findViewById(R.id.message); - String appName = getString(R.string.app_name); - String message = String.format(getString(R.string.sync_foreign_files_forgotten_explanation), appName, appName, appName, appName, mAccount.name); - textView.setText(message); - textView.setMovementMethod(new ScrollingMovementMethod()); - - /// load the list of local and remote files that failed - ListView listView = (ListView) findViewById(R.id.list); - if (mLocalPaths != null && mLocalPaths.size() > 0) { - mAdapter = new ErrorsWhileCopyingListAdapter(); - listView.setAdapter(mAdapter); - } else { - listView.setVisibility(View.GONE); - mAdapter = null; - } - - /// customize buttons - CustomButton cancelBtn = (CustomButton) findViewById(R.id.cancel); - CustomButton okBtn = (CustomButton) findViewById(R.id.ok); - - okBtn.setText(R.string.foreign_files_move); - cancelBtn.setOnClickListener(this); - okBtn.setOnClickListener(this); - } - - - /** - * Customized adapter, showing the local files as main text in two-lines list item and the remote files - * as the secondary text. - * - * @author David A. Velasco - */ - public class ErrorsWhileCopyingListAdapter extends ArrayAdapter { - - ErrorsWhileCopyingListAdapter() { - super(ErrorsWhileCopyingHandlerActivity.this, android.R.layout.two_line_list_item, android.R.id.text1, mLocalPaths); - } - - @Override - public boolean isEnabled(int position) { - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public View getView (int position, View convertView, ViewGroup parent) { - View view = convertView; - if (view == null) { - LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - view = vi.inflate(android.R.layout.two_line_list_item, null); - } - if (view != null) { - String localPath = getItem(position); - if (localPath != null) { - TextView text1 = (TextView) view.findViewById(android.R.id.text1); - if (text1 != null) { - text1.setText(String.format(getString(R.string.foreign_files_local_text), localPath)); - } - } - if (mRemotePaths != null && mRemotePaths.size() > 0 && position >= 0 && position < mRemotePaths.size()) { - TextView text2 = (TextView) view.findViewById(android.R.id.text2); - String remotePath = mRemotePaths.get(position); - if (text2 != null && remotePath != null) { - text2.setText(String.format(getString(R.string.foreign_files_remote_text), remotePath)); - } - } - } - return view; - } - } - - - /** - * Listener method to perform the MOVE / CANCEL action available in this activity. - * - * @param v Clicked view (button MOVE or CANCEL) - */ - @Override - public void onClick(View v) { - if (v.getId() == R.id.ok) { - /// perform movement operation in background thread - Log_OC.d(TAG, "Clicked MOVE, start movement"); - new MoveFilesTask().execute(); - - } else if (v.getId() == R.id.cancel) { - /// just finish - Log_OC.d(TAG, "Clicked CANCEL, bye"); - finish(); - - } else { - Log_OC.e(TAG, "Clicked phantom button, id: " + v.getId()); - } - } - - - /** - * Asynchronous task performing the move of all the local files to the ownCloud folder. - * - * @author David A. Velasco - */ - private class MoveFilesTask extends AsyncTask { - - /** - * Updates the UI before trying the movement - */ - @Override - protected void onPreExecute () { - /// progress dialog and disable 'Move' button - mCurrentDialog = IndeterminateProgressDialog.newInstance(R.string.wait_a_moment, false); - mCurrentDialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG); - findViewById(R.id.ok).setEnabled(false); - } - - - /** - * Performs the movement - * - * @return 'False' when the movement of any file fails. - */ - @Override - protected Boolean doInBackground(Void... params) { - while (!mLocalPaths.isEmpty()) { - String currentPath = mLocalPaths.get(0); - File currentFile = new File(currentPath); - String expectedPath = FileStorageUtils.getSavePath(mAccount.name) + mRemotePaths.get(0); - File expectedFile = new File(expectedPath); - - if (expectedFile.equals(currentFile) || currentFile.renameTo(expectedFile)) { - // SUCCESS - OCFile file = mStorageManager.getFileByPath(mRemotePaths.get(0)); - file.setStoragePath(expectedPath); - mStorageManager.saveFile(file); - mRemotePaths.remove(0); - mLocalPaths.remove(0); - - } else { - // FAIL - return false; - } - } - return true; - } - - /** - * Updates the activity UI after the movement of local files is tried. - * - * If the movement was successful for all the files, finishes the activity immediately. - * - * In other case, the list of remaining files is still available to retry the movement. - * - * @param result 'True' when the movement was successful. - */ - @Override - protected void onPostExecute(Boolean result) { - mAdapter.notifyDataSetChanged(); - mCurrentDialog.dismiss(); - mCurrentDialog = null; - findViewById(R.id.ok).setEnabled(true); - - if (result) { - // nothing else to do in this activity - Toast t = Toast.makeText(ErrorsWhileCopyingHandlerActivity.this, getString(R.string.foreign_files_success), Toast.LENGTH_LONG); - t.show(); - finish(); - - } else { - Toast t = Toast.makeText(ErrorsWhileCopyingHandlerActivity.this, getString(R.string.foreign_files_fail), Toast.LENGTH_LONG); - t.show(); - } - } - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/activity/FailedUploadActivity.java b/src/de/mobilcom/debitel/cloud/android/ui/activity/FailedUploadActivity.java deleted file mode 100644 index eeb8b275..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/activity/FailedUploadActivity.java +++ /dev/null @@ -1,56 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.activity; - -import android.app.Activity; -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.TextView; - -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.ui.CustomButton; - -/** - * This Activity is used to display a detail message for failed uploads - * - * The entry-point for this activity is the 'Failed upload Notification" - * - * @author andomaex / Matthias Baumann - */ -public class FailedUploadActivity extends Activity { - - public static final String MESSAGE = "message"; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.failed_upload_message_view); - String message = getIntent().getStringExtra(MESSAGE); - TextView textView = (TextView) findViewById(R.id.faild_upload_message); - textView.setText(message); - CustomButton closeBtn = (CustomButton) findViewById(R.id.failed_uploadactivity_close_button); - - closeBtn.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - finish(); - } - }); - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/activity/FileActivity.java b/src/de/mobilcom/debitel/cloud/android/ui/activity/FileActivity.java deleted file mode 100644 index f85b5fcd..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/activity/FileActivity.java +++ /dev/null @@ -1,320 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.ui.activity; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AccountManagerCallback; -import android.accounts.AccountManagerFuture; -import android.accounts.OperationCanceledException; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.webkit.MimeTypeMap; - -import com.actionbarsherlock.app.SherlockFragmentActivity; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; - -import eu.alefzero.webdav.WebdavUtils; - -/** - * Activity with common behaviour for activities handling {@link OCFile}s in ownCloud {@link Account}s . - * - * @author David A. Velasco - */ -public abstract class FileActivity extends SherlockFragmentActivity { - - public static final String EXTRA_FILE = "de.mobilcom.debitel.cloud.android.ui.activity.FILE"; - public static final String EXTRA_ACCOUNT = "de.mobilcom.debitel.cloud.android.ui.activity.ACCOUNT"; - public static final String EXTRA_WAITING_TO_PREVIEW = "de.mobilcom.debitel.cloud.android.ui.activity.WAITING_TO_PREVIEW"; - - public static final String TAG = FileActivity.class.getSimpleName(); - - - /** OwnCloud {@link Account} where the main {@link OCFile} handled by the activity is located. */ - private Account mAccount; - - /** Main {@link OCFile} handled by the activity.*/ - private OCFile mFile; - - /** Flag to signal that the activity will is finishing to enforce the creation of an ownCloud {@link Account} */ - private boolean mRedirectingToSetupAccount = false; - - /** Flag to signal when the value of mAccount was set */ - private boolean mAccountWasSet; - - /** Flag to signal when the value of mAccount was restored from a saved state */ - private boolean mAccountWasRestored; - - - /** - * Loads the ownCloud {@link Account} and main {@link OCFile} to be handled by the instance of - * the {@link FileActivity}. - * - * Grants that a valid ownCloud {@link Account} is associated to the instance, or that the user - * is requested to create a new one. - */ - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Account account; - if(savedInstanceState != null) { - account = savedInstanceState.getParcelable(FileActivity.EXTRA_ACCOUNT); - mFile = savedInstanceState.getParcelable(FileActivity.EXTRA_FILE); - } else { - account = getIntent().getParcelableExtra(FileActivity.EXTRA_ACCOUNT); - mFile = getIntent().getParcelableExtra(FileActivity.EXTRA_FILE); - } - - setAccount(account, savedInstanceState != null); - } - - - /** - * Since ownCloud {@link Account}s can be managed from the system setting menu, - * the existence of the {@link Account} associated to the instance must be checked - * every time it is restarted. - */ - @Override - protected void onRestart() { - super.onRestart(); - boolean validAccount = (mAccount != null && AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(), mAccount.name)); - if (!validAccount) { - swapToDefaultAccount(); - } - - } - - - @Override - protected void onStart() { - super.onStart(); - if (mAccountWasSet) { - onAccountSet(mAccountWasRestored); - } - } - - - /** - * Sets and validates the ownCloud {@link Account} associated to the Activity. - * - * If not valid, tries to swap it for other valid and existing ownCloud {@link Account}. - * - * POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}. - * - * @param account New {@link Account} to set. - * @param savedAccount When 'true', account was retrieved from a saved instance state. - */ - private void setAccount(Account account, boolean savedAccount) { - Account oldAccount = mAccount; - boolean validAccount = (account != null && AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(), account.name)); - if (validAccount) { - mAccount = account; - mAccountWasSet = true; - mAccountWasRestored = (savedAccount || mAccount.equals(oldAccount)); - - } else { - swapToDefaultAccount(); - } - } - - - /** - * Tries to swap the current ownCloud {@link Account} for other valid and existing. - * - * If no valid ownCloud {@link Account} exists, the the user is requested - * to create a new ownCloud {@link Account}. - * - * POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}. - * - * @return 'True' if the checked {@link Account} was valid. - */ - private void swapToDefaultAccount() { - // default to the most recently used account - Account newAccount = AccountUtils.getCurrentOwnCloudAccount(getApplicationContext()); - if (newAccount == null) { - /// no account available: force account creation - createFirstAccount(); - mRedirectingToSetupAccount = true; - mAccountWasSet = false; - mAccountWasRestored = false; - - } else { - mAccountWasSet = true; - mAccountWasRestored = (newAccount.equals(mAccount)); - mAccount = newAccount; - } - } - - - /** - * Launches the account creation activity. To use when no ownCloud account is available - */ - private void createFirstAccount() { - AccountManager am = AccountManager.get(getApplicationContext()); - am.addAccount(MainApp.getAccountType(), - null, - null, - null, - this, - new AccountCreationCallback(), - null); - } - - - /** - * {@inheritDoc} - */ - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putParcelable(FileActivity.EXTRA_FILE, mFile); - outState.putParcelable(FileActivity.EXTRA_ACCOUNT, mAccount); - } - - - /** - * Getter for the main {@link OCFile} handled by the activity. - * - * @return Main {@link OCFile} handled by the activity. - */ - public OCFile getFile() { - return mFile; - } - - - /** - * Setter for the main {@link OCFile} handled by the activity. - * - * @param file Main {@link OCFile} to be handled by the activity. - */ - public void setFile(OCFile file) { - mFile = file; - } - - - /** - * Getter for the ownCloud {@link Account} where the main {@link OCFile} handled by the activity is located. - * - * @return OwnCloud {@link Account} where the main {@link OCFile} handled by the activity is located. - */ - public Account getAccount() { - return mAccount; - } - - - /** - * @return 'True' when the Activity is finishing to enforce the setup of a new account. - */ - protected boolean isRedirectingToSetupAccount() { - return mRedirectingToSetupAccount; - } - - - /** - * Helper class handling a callback from the {@link AccountManager} after the creation of - * a new ownCloud {@link Account} finished, successfully or not. - * - * At this moment, only called after the creation of the first account. - * - * @author David A. Velasco - */ - public class AccountCreationCallback implements AccountManagerCallback { - - @Override - public void run(AccountManagerFuture future) { - FileActivity.this.mRedirectingToSetupAccount = false; - boolean accountWasSet = false; - if (future != null) { - try { - Bundle result; - result = future.getResult(); - String name = result.getString(AccountManager.KEY_ACCOUNT_NAME); - String type = result.getString(AccountManager.KEY_ACCOUNT_TYPE); - if (AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(), name)) { - setAccount(new Account(name, type), false); - accountWasSet = true; - } - } catch (OperationCanceledException e) { - Log_OC.d(TAG, "Account creation canceled"); - - } catch (Exception e) { - Log_OC.e(TAG, "Account creation finished in exception: ", e); - } - - } else { - Log_OC.e(TAG, "Account creation callback with null bundle"); - } - if (!accountWasSet) { - moveTaskToBack(true); - } - } - - } - - - /** - * Called when the ownCloud {@link Account} associated to the Activity was just updated. - * - * Child classes must grant that state depending on the {@link Account} is updated. - */ - protected abstract void onAccountSet(boolean stateWasRecovered); - - - - public void openFile(OCFile file) { - if (file != null) { - String storagePath = file.getStoragePath(); - String encodedStoragePath = WebdavUtils.encodePath(storagePath); - - Intent intentForSavedMimeType = new Intent(Intent.ACTION_VIEW); - intentForSavedMimeType.setDataAndType(Uri.parse("file://"+ encodedStoragePath), file.getMimetype()); - intentForSavedMimeType.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - - Intent intentForGuessedMimeType = null; - if (storagePath.lastIndexOf('.') >= 0) { - String guessedMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1)); - if (guessedMimeType != null && !guessedMimeType.equals(file.getMimetype())) { - intentForGuessedMimeType = new Intent(Intent.ACTION_VIEW); - intentForGuessedMimeType.setDataAndType(Uri.parse("file://"+ encodedStoragePath), guessedMimeType); - intentForGuessedMimeType.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - } - } - - Intent chooserIntent = null; - if (intentForGuessedMimeType != null) { - chooserIntent = Intent.createChooser(intentForGuessedMimeType, getString(R.string.actionbar_open_with)); - } else { - chooserIntent = Intent.createChooser(intentForSavedMimeType, getString(R.string.actionbar_open_with)); - } - - startActivity(chooserIntent); - - } else { - Log_OC.wtf(TAG, "Trying to open a NULL OCFile"); - } - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/activity/FileDisplayActivity.java b/src/de/mobilcom/debitel/cloud/android/ui/activity/FileDisplayActivity.java deleted file mode 100644 index c68783b8..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/activity/FileDisplayActivity.java +++ /dev/null @@ -1,1388 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.ui.activity; - -import java.io.File; - -import android.accounts.Account; -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.app.Dialog; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.content.SharedPreferences; -import android.content.res.Resources.NotFoundException; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.preference.PreferenceManager; -import android.provider.MediaStore; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.TextView; -import android.widget.Toast; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.ActionBar.OnNavigationListener; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -import com.actionbarsherlock.view.MenuItem; -import com.actionbarsherlock.view.Window; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.datamodel.DataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.FileDataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.files.services.FileDownloader; -import de.mobilcom.debitel.cloud.android.files.services.FileObserverService; -import de.mobilcom.debitel.cloud.android.files.services.FileUploader; -import de.mobilcom.debitel.cloud.android.files.services.FileDownloader.FileDownloaderBinder; -import de.mobilcom.debitel.cloud.android.files.services.FileUploader.FileUploaderBinder; -import de.mobilcom.debitel.cloud.android.operations.CreateFolderOperation; -import de.mobilcom.debitel.cloud.android.operations.OnRemoteOperationListener; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult; -import de.mobilcom.debitel.cloud.android.operations.RemoveFileOperation; -import de.mobilcom.debitel.cloud.android.operations.RenameFileOperation; -import de.mobilcom.debitel.cloud.android.operations.SynchronizeFileOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult.ResultCode; -import de.mobilcom.debitel.cloud.android.syncadapter.FileSyncService; -import de.mobilcom.debitel.cloud.android.ui.dialog.EditNameDialog; -import de.mobilcom.debitel.cloud.android.ui.dialog.LoadingDialog; -import de.mobilcom.debitel.cloud.android.ui.dialog.SslValidatorDialog; -import de.mobilcom.debitel.cloud.android.ui.dialog.EditNameDialog.EditNameDialogListener; -import de.mobilcom.debitel.cloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener; -import de.mobilcom.debitel.cloud.android.ui.fragment.FileDetailFragment; -import de.mobilcom.debitel.cloud.android.ui.fragment.FileFragment; -import de.mobilcom.debitel.cloud.android.ui.fragment.OCFileListFragment; -import de.mobilcom.debitel.cloud.android.ui.preview.PreviewImageActivity; -import de.mobilcom.debitel.cloud.android.ui.preview.PreviewMediaFragment; -import de.mobilcom.debitel.cloud.android.ui.preview.PreviewVideoActivity; - -/** - * Displays, what files the user has available in his ownCloud. - * - * @author Bartek Przybylski - * @author David A. Velasco - */ - -public class FileDisplayActivity extends FileActivity implements -OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener, OnSslValidatorListener, OnRemoteOperationListener, EditNameDialogListener { - - private ArrayAdapter mDirectories; - - /** Access point to the cached database for the current ownCloud {@link Account} */ - private DataStorageManager mStorageManager = null; - - private SyncBroadcastReceiver mSyncBroadcastReceiver; - private UploadFinishReceiver mUploadFinishReceiver; - private DownloadFinishReceiver mDownloadFinishReceiver; - private FileDownloaderBinder mDownloaderBinder = null; - private FileUploaderBinder mUploaderBinder = null; - private ServiceConnection mDownloadConnection = null, mUploadConnection = null; - private RemoteOperationResult mLastSslUntrustedServerResult = null; - - private boolean mDualPane; - private View mLeftFragmentContainer; - private View mRightFragmentContainer; - - private static final String KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW"; - - public static final int DIALOG_SHORT_WAIT = 0; - private static final int DIALOG_CHOOSE_UPLOAD_SOURCE = 1; - private static final int DIALOG_SSL_VALIDATOR = 2; - private static final int DIALOG_CERT_NOT_SAVED = 3; - - private static final String DIALOG_WAIT_TAG = "DIALOG_WAIT"; - - public static final String ACTION_DETAILS = "de.mobilcom.debitel.cloud.android.ui.activity.action.DETAILS"; - - private static final int ACTION_SELECT_CONTENT_FROM_APPS = 1; - private static final int ACTION_SELECT_MULTIPLE_FILES = 2; - - private static final String TAG = FileDisplayActivity.class.getSimpleName(); - - private static final String TAG_LIST_OF_FILES = "LIST_OF_FILES"; - private static final String TAG_SECOND_FRAGMENT = "SECOND_FRAGMENT"; - - private OCFile mWaitingToPreview; - private Handler mHandler; - - private String mDownloadAddedMessage; - private String mDownloadFinishMessage; - - @Override - protected void onCreate(Bundle savedInstanceState) { - Log_OC.d(TAG, "onCreate() start"); - requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - - super.onCreate(savedInstanceState); // this calls onAccountChanged() when ownCloud Account is valid - - mHandler = new Handler(); - - FileDownloader downloader = new FileDownloader(); - mDownloadAddedMessage = downloader.getDownloadAddedMessage(); - mDownloadFinishMessage= downloader.getDownloadFinishMessage(); - - /// bindings to transference services - mUploadConnection = new ListServiceConnection(); - mDownloadConnection = new ListServiceConnection(); - bindService(new Intent(this, FileUploader.class), mUploadConnection, Context.BIND_AUTO_CREATE); - bindService(new Intent(this, FileDownloader.class), mDownloadConnection, Context.BIND_AUTO_CREATE); - - // PIN CODE request ; best location is to decide, let's try this first - if (getIntent().getAction() != null && getIntent().getAction().equals(Intent.ACTION_MAIN) && savedInstanceState == null) { - requestPinCode(); - } - - /// file observer - Intent observer_intent = new Intent(this, FileObserverService.class); - observer_intent.putExtra(FileObserverService.KEY_FILE_CMD, FileObserverService.CMD_INIT_OBSERVED_LIST); - startService(observer_intent); - - /// Load of saved instance state - if(savedInstanceState != null) { - mWaitingToPreview = (OCFile) savedInstanceState.getParcelable(FileDisplayActivity.KEY_WAITING_TO_PREVIEW); - - } else { - mWaitingToPreview = null; - } - - /// USER INTERFACE - - // Inflate and set the layout view - setContentView(R.layout.files); - mDualPane = getResources().getBoolean(R.bool.large_land_layout); - mLeftFragmentContainer = findViewById(R.id.left_fragment_container); - mRightFragmentContainer = findViewById(R.id.right_fragment_container); - if (savedInstanceState == null) { - createMinFragments(); - } - - // Action bar setup - mDirectories = new CustomArrayAdapter(this, R.layout.sherlock_spinner_dropdown_item); - getSupportActionBar().setHomeButtonEnabled(true); // mandatory since Android ICS, according to the official documentation - setSupportProgressBarIndeterminateVisibility(false); // always AFTER setContentView(...) ; to work around bug in its implementation - - - - Log_OC.d(TAG, "onCreate() end"); - } - - - @Override - protected void onDestroy() { - super.onDestroy(); - if (mDownloadConnection != null) - unbindService(mDownloadConnection); - if (mUploadConnection != null) - unbindService(mUploadConnection); - } - - - /** - * Called when the ownCloud {@link Account} associated to the Activity was just updated. - */ - @Override - protected void onAccountSet(boolean stateWasRecovered) { - if (getAccount() != null) { - mStorageManager = new FileDataStorageManager(getAccount(), getContentResolver()); - - /// Check whether the 'main' OCFile handled by the Activity is contained in the current Account - OCFile file = getFile(); - // get parent from path - String parentPath = ""; - if (file != null) { - if (file.isDown() && file.getLastSyncDateForProperties() == 0) { - // upload in progress - right now, files are not inserted in the local cache until the upload is successful - // get parent from path - parentPath = file.getRemotePath().substring(0, file.getRemotePath().lastIndexOf(file.getFileName())); - if (mStorageManager.getFileByPath(parentPath) == null) - file = null; // not able to know the directory where the file is uploading - } else { - file = mStorageManager.getFileByPath(file.getRemotePath()); // currentDir = null if not in the current Account - } - } - if (file == null) { - // fall back to root folder - file = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR); // never returns null - } - setFile(file); - mDirectories.clear(); - OCFile fileIt = file; - while(fileIt != null && fileIt.getFileName() != OCFile.PATH_SEPARATOR) { - if (fileIt.isDirectory()) { - mDirectories.add(fileIt.getFileName()); - } - // get parent from path - parentPath = fileIt.getRemotePath().substring(0, fileIt.getRemotePath().lastIndexOf(fileIt.getFileName())); - fileIt = mStorageManager.getFileByPath(parentPath); - } - mDirectories.add(OCFile.PATH_SEPARATOR); - if (!stateWasRecovered) { - Log_OC.e(TAG, "Initializing Fragments in onAccountChanged.."); - initFragmentsWithFile(); - - } else { - updateFragmentsVisibility(!file.isDirectory()); - updateNavigationElementsInActionBar(file.isDirectory() ? null : file); - } - - - } else { - Log_OC.wtf(TAG, "onAccountChanged was called with NULL account associated!"); - } - } - - - private void createMinFragments() { - OCFileListFragment listOfFiles = new OCFileListFragment(); - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - 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(); - if (listOfFiles != null) { - listOfFiles.listDirectory(getCurrentDir()); - } else { - Log.e(TAG, "Still have a chance to lose the initializacion of list fragment >("); - } - - /// Second fragment - OCFile file = getFile(); - Fragment secondFragment = chooseInitialSecondFragment(file); - if (secondFragment != null) { - setSecondFragment(secondFragment); - updateFragmentsVisibility(true); - updateNavigationElementsInActionBar(file); - - } else { - cleanSecondFragment(); - } - - } else { - Log.wtf(TAG, "initFragments() called with invalid NULLs!"); - if (getAccount() == null) { - Log.wtf(TAG, "\t account is NULL"); - } - if (getFile() == null) { - Log.wtf(TAG, "\t file is NULL"); - } - } - } - - private Fragment chooseInitialSecondFragment(OCFile file) { - Fragment secondFragment = null; - if (file != null && !file.isDirectory()) { - if (file.isDown() && PreviewMediaFragment.canBePreviewed(file) - && file.getLastSyncDateForProperties() > 0 // temporal fix - ) { - int startPlaybackPosition = getIntent().getIntExtra(PreviewVideoActivity.EXTRA_START_POSITION, 0); - boolean autoplay = getIntent().getBooleanExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, true); - secondFragment = new PreviewMediaFragment(file, getAccount(), startPlaybackPosition, autoplay); - - } else { - secondFragment = new FileDetailFragment(file, getAccount()); - } - } - return secondFragment; - } - - - /** - * 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. - * - * @param fragment New second Fragment to set. - */ - private void setSecondFragment(Fragment fragment) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.right_fragment_container, fragment, TAG_SECOND_FRAGMENT); - transaction.commit(); - } - - - private void updateFragmentsVisibility(boolean existsSecondFragment) { - if (mDualPane) { - if (mLeftFragmentContainer.getVisibility() != View.VISIBLE) { - mLeftFragmentContainer.setVisibility(View.VISIBLE); - } - if (mRightFragmentContainer.getVisibility() != View.VISIBLE) { - mRightFragmentContainer.setVisibility(View.VISIBLE); - } - - } else if (existsSecondFragment) { - if (mLeftFragmentContainer.getVisibility() != View.GONE) { - mLeftFragmentContainer.setVisibility(View.GONE); - } - if (mRightFragmentContainer.getVisibility() != View.VISIBLE) { - mRightFragmentContainer.setVisibility(View.VISIBLE); - } - - } else { - if (mLeftFragmentContainer.getVisibility() != View.VISIBLE) { - mLeftFragmentContainer.setVisibility(View.VISIBLE); - } - if (mRightFragmentContainer.getVisibility() != View.GONE) { - mRightFragmentContainer.setVisibility(View.GONE); - } - } - } - - - private OCFileListFragment getListOfFilesFragment() { - Fragment listOfFiles = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES); - if (listOfFiles != null) { - return (OCFileListFragment)listOfFiles; - } - Log_OC.wtf(TAG, "Access to unexisting list of files fragment!!"); - return null; - } - - protected FileFragment getSecondFragment() { - Fragment second = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_SECOND_FRAGMENT); - if (second != null) { - return (FileFragment)second; - } - return null; - } - - public void cleanSecondFragment() { - Fragment second = getSecondFragment(); - if (second != null) { - FragmentTransaction tr = getSupportFragmentManager().beginTransaction(); - tr.remove(second); - tr.commit(); - } - updateFragmentsVisibility(false); - updateNavigationElementsInActionBar(null); - } - - protected void refeshListOfFilesFragment() { - OCFileListFragment fileListFragment = getListOfFilesFragment(); - if (fileListFragment != null) { - fileListFragment.listDirectory(); - } - } - - protected void refreshSecondFragment(String downloadEvent, String downloadedRemotePath, boolean success) { - FileFragment secondFragment = getSecondFragment(); - boolean waitedPreview = (mWaitingToPreview != null && mWaitingToPreview.getRemotePath().equals(downloadedRemotePath)); - if (secondFragment != null && secondFragment instanceof FileDetailFragment) { - FileDetailFragment detailsFragment = (FileDetailFragment) secondFragment; - OCFile fileInFragment = detailsFragment.getFile(); - if (fileInFragment != null && !downloadedRemotePath.equals(fileInFragment.getRemotePath())) { - // the user browsed to other file ; forget the automatic preview - mWaitingToPreview = null; - - } else if (downloadEvent.equals(mDownloadAddedMessage)) { - // grant that the right panel updates the progress bar - detailsFragment.listenForTransferProgress(); - detailsFragment.updateFileDetails(true, false); - - } else if (downloadEvent.equals(mDownloadFinishMessage)) { - // update the right panel - boolean detailsFragmentChanged = false; - if (waitedPreview) { - if (success) { - mWaitingToPreview = mStorageManager.getFileById(mWaitingToPreview.getFileId()); // update the file from database, for the local storage path - if (PreviewMediaFragment.canBePreviewed(mWaitingToPreview)) { - startMediaPreview(mWaitingToPreview, 0, true); - detailsFragmentChanged = true; - } else { - openFile(mWaitingToPreview); - } - } - mWaitingToPreview = null; - } - if (!detailsFragmentChanged) { - detailsFragment.updateFileDetails(false, (success)); - } - } - } - } - - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getSherlock().getMenuInflater(); - inflater.inflate(R.menu.main_menu, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - boolean retval = true; - switch (item.getItemId()) { - case R.id.action_create_dir: { - EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.uploader_info_dirname), "", -1, -1, this); - dialog.show(getSupportFragmentManager(), "createdirdialog"); - break; - } - case R.id.action_sync_account: { - startSynchronization(); - break; - } - case R.id.action_upload: { - showDialog(DIALOG_CHOOSE_UPLOAD_SOURCE); - break; - } - case R.id.action_settings: { - Intent settingsIntent = new Intent(this, Preferences.class); - startActivity(settingsIntent); - break; - } - case android.R.id.home: { - FileFragment second = getSecondFragment(); - OCFile currentDir = getCurrentDir(); - if((currentDir != null && currentDir.getParentId() != 0) || - (second != null && second.getFile() != null)) { - onBackPressed(); - } - break; - } - default: - retval = super.onOptionsItemSelected(item); - } - return retval; - } - - private void startSynchronization() { - ContentResolver.cancelSync(null, MainApp.getAuthTokenType()); // cancel the current synchronizations of any ownCloud account - Bundle bundle = new Bundle(); - bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - ContentResolver.requestSync( - getAccount(), - MainApp.getAuthTokenType(), bundle); - } - - - @Override - public boolean onNavigationItemSelected(int itemPosition, long itemId) { - int i = itemPosition; - while (i-- != 0) { - onBackPressed(); - } - // the next operation triggers a new call to this method, but it's necessary to - // ensure that the name exposed in the action bar is the current directory when the - // user selected it in the navigation list - if (itemPosition != 0) - getSupportActionBar().setSelectedNavigationItem(0); - return true; - } - - /** - * Called, when the user selected something for uploading - */ - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (requestCode == ACTION_SELECT_CONTENT_FROM_APPS && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) { - requestSimpleUpload(data, resultCode); - - } else if (requestCode == ACTION_SELECT_MULTIPLE_FILES && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) { - requestMultipleUpload(data, resultCode); - - } - } - - private void requestMultipleUpload(Intent data, int resultCode) { - String[] filePaths = data.getStringArrayExtra(UploadFilesActivity.EXTRA_CHOSEN_FILES); - if (filePaths != null) { - String[] remotePaths = new String[filePaths.length]; - String remotePathBase = ""; - for (int j = mDirectories.getCount() - 2; j >= 0; --j) { - remotePathBase += OCFile.PATH_SEPARATOR + mDirectories.getItem(j); - } - if (!remotePathBase.endsWith(OCFile.PATH_SEPARATOR)) - remotePathBase += OCFile.PATH_SEPARATOR; - for (int j = 0; j< remotePaths.length; j++) { - 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); - if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) - i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE); - startService(i); - - } else { - Log_OC.d(TAG, "User clicked on 'Update' with no selection"); - Toast t = Toast.makeText(this, getString(R.string.filedisplay_no_file_selected), Toast.LENGTH_LONG); - t.show(); - return; - } - } - - - private void requestSimpleUpload(Intent data, int resultCode) { - String filepath = null; - try { - Uri selectedImageUri = data.getData(); - - String filemanagerstring = selectedImageUri.getPath(); - String selectedImagePath = getPath(selectedImageUri); - - if (selectedImagePath != null) - filepath = selectedImagePath; - else - filepath = filemanagerstring; - - } catch (Exception e) { - Log_OC.e(TAG, "Unexpected exception when trying to read the result of Intent.ACTION_GET_CONTENT", e); - e.printStackTrace(); - - } finally { - if (filepath == null) { - Log_OC.e(TAG, "Couldnt resolve path to file"); - Toast t = Toast.makeText(this, getString(R.string.filedisplay_unexpected_bad_get_content), Toast.LENGTH_LONG); - t.show(); - return; - } - } - - Intent i = new Intent(this, FileUploader.class); - i.putExtra(FileUploader.KEY_ACCOUNT, - getAccount()); - String remotepath = new String(); - for (int j = mDirectories.getCount() - 2; j >= 0; --j) { - remotepath += OCFile.PATH_SEPARATOR + mDirectories.getItem(j); - } - if (!remotepath.endsWith(OCFile.PATH_SEPARATOR)) - remotepath += OCFile.PATH_SEPARATOR; - remotepath += new File(filepath).getName(); - - i.putExtra(FileUploader.KEY_LOCAL_FILE, filepath); - i.putExtra(FileUploader.KEY_REMOTE_FILE, remotepath); - 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); - } - - @Override - public void onBackPressed() { - OCFileListFragment listOfFiles = getListOfFilesFragment(); - if (mDualPane || getSecondFragment() == null) { - if (listOfFiles != null) { // should never be null, indeed - if (mDirectories.getCount() <= 1) { - finish(); - return; - } - popDirname(); - listOfFiles.onBrowseUp(); - } - } - if (listOfFiles != null) { // should never be null, indeed - setFile(listOfFiles.getCurrentFile()); - } - cleanSecondFragment(); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - // responsibility of restore is preferred in onCreate() before than in onRestoreInstanceState when there are Fragments involved - Log_OC.e(TAG, "onSaveInstanceState() start"); - super.onSaveInstanceState(outState); - outState.putParcelable(FileDisplayActivity.KEY_WAITING_TO_PREVIEW, mWaitingToPreview); - Log_OC.d(TAG, "onSaveInstanceState() end"); - } - - - - @Override - protected void onResume() { - super.onResume(); - Log_OC.e(TAG, "onResume() start"); - - FileUploader fileUploader = new FileUploader(); - FileSyncService fileSyncService = new FileSyncService(); - - // Listen for sync messages - IntentFilter syncIntentFilter = new IntentFilter(fileSyncService.getSyncMessage()); - mSyncBroadcastReceiver = new SyncBroadcastReceiver(); - registerReceiver(mSyncBroadcastReceiver, syncIntentFilter); - - // Listen for upload messages - IntentFilter uploadIntentFilter = new IntentFilter(fileUploader.getUploadFinishMessage()); - mUploadFinishReceiver = new UploadFinishReceiver(); - registerReceiver(mUploadFinishReceiver, uploadIntentFilter); - - // Listen for download messages - IntentFilter downloadIntentFilter = new IntentFilter(mDownloadAddedMessage); - downloadIntentFilter.addAction(mDownloadFinishMessage); - mDownloadFinishReceiver = new DownloadFinishReceiver(); - registerReceiver(mDownloadFinishReceiver, downloadIntentFilter); - - Log_OC.d(TAG, "onResume() end"); - } - - - @Override - protected void onPause() { - super.onPause(); - Log_OC.e(TAG, "onPause() start"); - if (mSyncBroadcastReceiver != null) { - unregisterReceiver(mSyncBroadcastReceiver); - mSyncBroadcastReceiver = null; - } - if (mUploadFinishReceiver != null) { - unregisterReceiver(mUploadFinishReceiver); - mUploadFinishReceiver = null; - } - if (mDownloadFinishReceiver != null) { - unregisterReceiver(mDownloadFinishReceiver); - mDownloadFinishReceiver = null; - } - - Log_OC.d(TAG, "onPause() end"); - } - - - @Override - protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { - if (id == DIALOG_SSL_VALIDATOR && mLastSslUntrustedServerResult != null) { - ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult); - } - } - - - @Override - protected Dialog onCreateDialog(int id) { - Dialog dialog = null; - AlertDialog.Builder builder; - switch (id) { - case DIALOG_SHORT_WAIT: { - ProgressDialog working_dialog = new ProgressDialog(this); - working_dialog.setMessage(getResources().getString( - R.string.wait_a_moment)); - working_dialog.setIndeterminate(true); - working_dialog.setCancelable(false); - dialog = working_dialog; - break; - } - case DIALOG_CHOOSE_UPLOAD_SOURCE: { - - String[] items = null; - - String[] allTheItems = { getString(R.string.actionbar_upload_files), - getString(R.string.actionbar_upload_from_apps), - getString(R.string.actionbar_failed_instant_upload) }; - - String[] commonItems = { getString(R.string.actionbar_upload_files), - getString(R.string.actionbar_upload_from_apps) }; - - if (InstantUploadActivity.IS_ENABLED) - items = allTheItems; - else - items = commonItems; - - builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.actionbar_upload); - builder.setItems(items, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int item) { - if (item == 0) { - // if (!mDualPane) { - Intent action = new Intent(FileDisplayActivity.this, UploadFilesActivity.class); - action.putExtra(UploadFilesActivity.EXTRA_ACCOUNT, FileDisplayActivity.this.getAccount()); - startActivityForResult(action, ACTION_SELECT_MULTIPLE_FILES); - // } else { - // TODO create and handle new fragment - // LocalFileListFragment - // } - } else if (item == 1) { - Intent action = new Intent(Intent.ACTION_GET_CONTENT); - action = action.setType("*/*").addCategory(Intent.CATEGORY_OPENABLE); - startActivityForResult(Intent.createChooser(action, getString(R.string.upload_chooser_title)), - ACTION_SELECT_CONTENT_FROM_APPS); - } else if (item == 2 && InstantUploadActivity.IS_ENABLED) { - Intent action = new Intent(FileDisplayActivity.this, InstantUploadActivity.class); - action.putExtra(FileUploader.KEY_ACCOUNT, FileDisplayActivity.this.getAccount()); - startActivity(action); - } - } - }); - dialog = builder.create(); - break; - } - case DIALOG_SSL_VALIDATOR: { - dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this); - break; - } - case DIALOG_CERT_NOT_SAVED: { - builder = new AlertDialog.Builder(this); - builder.setMessage(getResources().getString(R.string.ssl_validator_not_saved)); - builder.setCancelable(false); - builder.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - }; - }); - dialog = builder.create(); - break; - } - default: - dialog = null; - } - - return dialog; - } - - - /** - * Show loading dialog - */ - public void showLoadingDialog() { - // Construct dialog - LoadingDialog loading = new LoadingDialog(getResources().getString(R.string.wait_a_moment)); - FragmentManager fm = getSupportFragmentManager(); - FragmentTransaction ft = fm.beginTransaction(); - loading.show(ft, DIALOG_WAIT_TAG); - - } - - /** - * Dismiss loading dialog - */ - public void dismissLoadingDialog(){ - Fragment frag = getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG); - if (frag != null) { - LoadingDialog loading = (LoadingDialog) frag; - loading.dismiss(); - } - } - - - /** - * Translates a content URI of an image to a physical path - * on the disk - * @param uri The URI to resolve - * @return The path to the image or null if it could not be found - */ - public String getPath(Uri uri) { - String[] projection = { MediaStore.Images.Media.DATA }; - Cursor cursor = managedQuery(uri, projection, null, null, null); - if (cursor != null) { - int column_index = cursor - .getColumnIndexOrThrow(MediaStore.Images.Media.DATA); - cursor.moveToFirst(); - return cursor.getString(column_index); - } - return null; - } - - /** - * Pushes a directory to the drop down list - * @param directory to push - * @throws IllegalArgumentException If the {@link OCFile#isDirectory()} returns false. - */ - public void pushDirname(OCFile directory) { - if(!directory.isDirectory()){ - throw new IllegalArgumentException("Only directories may be pushed!"); - } - mDirectories.insert(directory.getFileName(), 0); - setFile(directory); - } - - /** - * Pops a directory name from the drop down list - * @return True, unless the stack is empty - */ - public boolean popDirname() { - mDirectories.remove(mDirectories.getItem(0)); - return !mDirectories.isEmpty(); - } - - // Custom array adapter to override text colors - private class CustomArrayAdapter extends ArrayAdapter { - - public CustomArrayAdapter(FileDisplayActivity ctx, int view) { - super(ctx, view); - } - - public View getView(int position, View convertView, ViewGroup parent) { - View v = super.getView(position, convertView, parent); - - ((TextView) v).setTextColor(getResources().getColorStateList( - android.R.color.white)); - return v; - } - - public View getDropDownView(int position, View convertView, - ViewGroup parent) { - View v = super.getDropDownView(position, convertView, parent); - - ((TextView) v).setTextColor(getResources().getColorStateList( - android.R.color.white)); - - return v; - } - - } - - private class SyncBroadcastReceiver extends BroadcastReceiver { - - /** - * {@link BroadcastReceiver} to enable syncing feedback in UI - */ - @Override - public void onReceive(Context context, Intent intent) { - boolean inProgress = intent.getBooleanExtra(FileSyncService.IN_PROGRESS, false); - String accountName = intent.getStringExtra(FileSyncService.ACCOUNT_NAME); - - Log_OC.d(TAG, "sync of account " + accountName + " is in_progress: " + inProgress); - - if (getAccount() != null && accountName.equals(getAccount().name) - && mStorageManager != null - ) { - - String synchFolderRemotePath = intent.getStringExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH); - - boolean fillBlankRoot = false; - OCFile currentDir = getCurrentDir(); - if (currentDir == null) { - currentDir = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR); - fillBlankRoot = (currentDir != null); - } - - if ((synchFolderRemotePath != null && currentDir != null && (currentDir.getRemotePath().equals(synchFolderRemotePath))) - || fillBlankRoot ) { - if (!fillBlankRoot) - currentDir = mStorageManager.getFileByPath(synchFolderRemotePath); - OCFileListFragment fileListFragment = getListOfFilesFragment(); - if (fileListFragment != null) { - fileListFragment.listDirectory(currentDir); - } - if (getSecondFragment() == null) - setFile(currentDir); - } - - setSupportProgressBarIndeterminateVisibility(inProgress); - removeStickyBroadcast(intent); - - } - - RemoteOperationResult synchResult = (RemoteOperationResult)intent.getSerializableExtra(FileSyncService.SYNC_RESULT); - if (synchResult != null) { - if (synchResult.getCode().equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED)) { - mLastSslUntrustedServerResult = synchResult; - showDialog(DIALOG_SSL_VALIDATOR); - } - } - } - } - - - private class UploadFinishReceiver extends BroadcastReceiver { - /** - * Once the file upload has finished -> update view - * @author David A. Velasco - * {@link BroadcastReceiver} to enable upload feedback in UI - */ - @Override - public void onReceive(Context context, Intent intent) { - String uploadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); - String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME); - boolean sameAccount = getAccount() != null && accountName.equals(getAccount().name); - OCFile currentDir = getCurrentDir(); - boolean isDescendant = (currentDir != null) && (uploadedRemotePath != null) && (uploadedRemotePath.startsWith(currentDir.getRemotePath())); - if (sameAccount && isDescendant) { - refeshListOfFilesFragment(); - } - } - - } - - - /** - * Class waiting for broadcast events from the {@link FielDownloader} service. - * - * Updates the UI when a download is started or finished, provided that it is relevant for the - * current folder. - */ - private class DownloadFinishReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - boolean sameAccount = isSameAccount(context, intent); - String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); - boolean isDescendant = isDescendant(downloadedRemotePath); - - if (sameAccount && isDescendant) { - refeshListOfFilesFragment(); - refreshSecondFragment(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false)); - } - - removeStickyBroadcast(intent); - } - - private boolean isDescendant(String downloadedRemotePath) { - OCFile currentDir = getCurrentDir(); - return (currentDir != null && downloadedRemotePath != null && downloadedRemotePath.startsWith(currentDir.getRemotePath())); - } - - private boolean isSameAccount(Context context, Intent intent) { - String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME); - return (accountName != null && getAccount() != null && accountName.equals(getAccount().name)); - } - } - - - /** - * {@inheritDoc} - */ - @Override - public DataStorageManager getStorageManager() { - return mStorageManager; - } - - - /** - * {@inheritDoc} - * - * Updates action bar and second fragment, if in dual pane mode. - */ - @Override - public void onBrowsedDownTo(OCFile directory) { - pushDirname(directory); - cleanSecondFragment(); - } - - /** - * Opens the image gallery showing the image {@link OCFile} received as parameter. - * - * @param file Image {@link OCFile} to show. - */ - @Override - public void startImagePreview(OCFile file) { - Intent showDetailsIntent = new Intent(this, PreviewImageActivity.class); - showDetailsIntent.putExtra(EXTRA_FILE, 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. - * @param autoplay When 'true', the playback will start without user interactions. - */ - @Override - public void startMediaPreview(OCFile file, int startPlaybackPosition, boolean autoplay) { - Fragment mediaFragment = new PreviewMediaFragment(file, getAccount(), startPlaybackPosition, autoplay); - setSecondFragment(mediaFragment); - updateFragmentsVisibility(true); - updateNavigationElementsInActionBar(file); - setFile(file); - } - - /** - * 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. - */ - @Override - public void startDownloadForPreview(OCFile file) { - Fragment detailFragment = new FileDetailFragment(file, getAccount()); - setSecondFragment(detailFragment); - mWaitingToPreview = file; - requestForDownload(); - updateFragmentsVisibility(true); - updateNavigationElementsInActionBar(file); - setFile(file); - } - - - /** - * 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 - public void showDetails(OCFile file) { - Fragment detailFragment = new FileDetailFragment(file, getAccount()); - setSecondFragment(detailFragment); - updateFragmentsVisibility(true); - updateNavigationElementsInActionBar(file); - setFile(file); - } - - - /** - * TODO - */ - private void updateNavigationElementsInActionBar(OCFile chosenFile) { - ActionBar actionBar = getSupportActionBar(); - if (chosenFile == null || mDualPane) { - // only list of files - set for browsing through folders - OCFile currentDir = getCurrentDir(); - actionBar.setDisplayHomeAsUpEnabled(currentDir != null && currentDir.getParentId() != 0); - actionBar.setDisplayShowTitleEnabled(false); - actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); - actionBar.setListNavigationCallbacks(mDirectories, this); // assuming mDirectories is updated - - } else { - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setTitle(chosenFile.getFileName()); - actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); - } - } - - - /** - * {@inheritDoc} - */ - @Override - public void onFileStateChanged() { - refeshListOfFilesFragment(); - updateNavigationElementsInActionBar(getSecondFragment().getFile()); - } - - - /** - * {@inheritDoc} - */ - @Override - public FileDownloaderBinder getFileDownloaderBinder() { - return mDownloaderBinder; - } - - - /** - * {@inheritDoc} - */ - @Override - public FileUploaderBinder getFileUploaderBinder() { - return mUploaderBinder; - } - - - /** Defines callbacks for service binding, passed to bindService() */ - private class ListServiceConnection implements ServiceConnection { - - @Override - public void onServiceConnected(ComponentName component, IBinder service) { - if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloader.class))) { - Log_OC.d(TAG, "Download service connected"); - mDownloaderBinder = (FileDownloaderBinder) service; - if (mWaitingToPreview != null) { - requestForDownload(); - } - - } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) { - Log_OC.d(TAG, "Upload service connected"); - mUploaderBinder = (FileUploaderBinder) service; - } else { - return; - } - // a new chance to get the mDownloadBinder through getFileDownloadBinder() - THIS IS A MESS - OCFileListFragment listOfFiles = getListOfFilesFragment(); - if (listOfFiles != null) { - listOfFiles.listDirectory(); - } - FileFragment secondFragment = getSecondFragment(); - if (secondFragment != null && secondFragment instanceof FileDetailFragment) { - FileDetailFragment detailFragment = (FileDetailFragment)secondFragment; - detailFragment.listenForTransferProgress(); - detailFragment.updateFileDetails(false, false); - } - } - - @Override - public void onServiceDisconnected(ComponentName component) { - if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloader.class))) { - Log_OC.d(TAG, "Download service disconnected"); - mDownloaderBinder = null; - } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) { - Log_OC.d(TAG, "Upload service disconnected"); - mUploaderBinder = null; - } - } - }; - - - - /** - * Launch an intent to request the PIN code to the user before letting him use the app - */ - private void requestPinCode() { - boolean pinStart = false; - SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - pinStart = appPrefs.getBoolean("set_pincode", false); - if (pinStart) { - Intent i = new Intent(getApplicationContext(), PinCodeActivity.class); - i.putExtra(PinCodeActivity.EXTRA_ACTIVITY, "FileDisplayActivity"); - startActivity(i); - } - } - - - @Override - public void onSavedCertificate() { - startSynchronization(); - } - - - @Override - public void onFailedSavingCertificate() { - showDialog(DIALOG_CERT_NOT_SAVED); - } - - - /** - * 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) { - if (operation instanceof RemoveFileOperation) { - onRemoveFileOperationFinish((RemoveFileOperation)operation, result); - - } else if (operation instanceof RenameFileOperation) { - onRenameFileOperationFinish((RenameFileOperation)operation, result); - - } else if (operation instanceof SynchronizeFileOperation) { - onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result); - - } else if (operation instanceof CreateFolderOperation) { - onCreateFolderOperationFinish((CreateFolderOperation)operation, result); - } - } - - - /** - * 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. - */ - private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) { - dismissLoadingDialog(); - if (result.isSuccess()) { - Toast msg = Toast.makeText(this, R.string.remove_success_msg, Toast.LENGTH_LONG); - msg.show(); - OCFile removedFile = operation.getFile(); - getSecondFragment(); - FileFragment second = getSecondFragment(); - if (second != null && removedFile.equals(second.getFile())) { - cleanSecondFragment(); - } - if (mStorageManager.getFileById(removedFile.getParentId()).equals(getCurrentDir())) { - refeshListOfFilesFragment(); - } - - } else { - Toast msg = Toast.makeText(this, R.string.remove_fail_msg, Toast.LENGTH_LONG); - msg.show(); - if (result.isSslRecoverableException()) { - mLastSslUntrustedServerResult = result; - showDialog(DIALOG_SSL_VALIDATOR); - } - } - } - - /** - * 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. - */ - private void onCreateFolderOperationFinish(CreateFolderOperation operation, RemoteOperationResult result) { - if (result.isSuccess()) { - dismissLoadingDialog(); - refeshListOfFilesFragment(); - - } else { - //dismissDialog(DIALOG_SHORT_WAIT); - dismissLoadingDialog(); - try { - Toast msg = Toast.makeText(FileDisplayActivity.this, R.string.create_dir_fail_msg, Toast.LENGTH_LONG); - msg.show(); - - } catch (NotFoundException e) { - Log_OC.e(TAG, "Error while trying to show fail message " , e); - } - } - } - - - /** - * 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. - */ - private void onRenameFileOperationFinish(RenameFileOperation operation, RemoteOperationResult result) { - dismissLoadingDialog(); - OCFile renamedFile = operation.getFile(); - if (result.isSuccess()) { - if (mDualPane) { - FileFragment details = getSecondFragment(); - if (details != null && details instanceof FileDetailFragment && renamedFile.equals(details.getFile()) ) { - ((FileDetailFragment) details).updateFileDetails(renamedFile, getAccount()); - } - } - if (mStorageManager.getFileById(renamedFile.getParentId()).equals(getCurrentDir())) { - refeshListOfFilesFragment(); - } - - } else { - if (result.getCode().equals(ResultCode.INVALID_LOCAL_FILE_NAME)) { - Toast msg = Toast.makeText(this, R.string.rename_local_fail_msg, Toast.LENGTH_LONG); - msg.show(); - // TODO throw again the new rename dialog - } else { - Toast msg = Toast.makeText(this, R.string.rename_server_fail_msg, Toast.LENGTH_LONG); - msg.show(); - if (result.isSslRecoverableException()) { - mLastSslUntrustedServerResult = result; - showDialog(DIALOG_SSL_VALIDATOR); - } - } - } - } - - - private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) { - dismissLoadingDialog(); - OCFile syncedFile = operation.getLocalFile(); - 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_ACCOUNT, getAccount()); - startActivity(i); - - } else { - Toast msg = Toast.makeText(this, R.string.sync_file_fail_msg, Toast.LENGTH_LONG); - msg.show(); - } - - } else { - if (operation.transferWasRequested()) { - refeshListOfFilesFragment(); - onTransferStateChanged(syncedFile, true, true); - - } else { - Toast msg = Toast.makeText(this, R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG); - msg.show(); - } - } - } - - - /** - * {@inheritDoc} - */ - @Override - public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading) { - if (mDualPane) { - FileFragment details = getSecondFragment(); - if (details != null && details instanceof FileDetailFragment && file.equals(details.getFile()) ) { - if (downloading || uploading) { - ((FileDetailFragment)details).updateFileDetails(file, getAccount()); - } else { - ((FileDetailFragment)details).updateFileDetails(false, true); - } - } - } - } - - - public void onDismiss(EditNameDialog dialog) { - if (dialog.getResult()) { - String newDirectoryName = dialog.getNewFilename().trim(); - Log_OC.d(TAG, "'create directory' dialog dismissed with new name " + newDirectoryName); - if (newDirectoryName.length() > 0) { - String path = getCurrentDir().getRemotePath(); - - // Create directory - path += newDirectoryName + OCFile.PATH_SEPARATOR; - RemoteOperation operation = new CreateFolderOperation(path, false, mStorageManager); - operation.execute( getAccount(), - FileDisplayActivity.this, - FileDisplayActivity.this, - mHandler, - FileDisplayActivity.this); - - showLoadingDialog(); - } - } - } - - - private void requestForDownload() { - 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, mWaitingToPreview); - startService(i); - } - } - - - private OCFile getCurrentDir() { - OCFile file = getFile(); - if (file != null) { - if (file.isDirectory()) { - return file; - } else if (mStorageManager != null) { - String parentPath = file.getRemotePath().substring(0, file.getRemotePath().lastIndexOf(file.getFileName())); - return mStorageManager.getFileByPath(parentPath); - } - } - return null; - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/activity/GenericExplanationActivity.java b/src/de/mobilcom/debitel/cloud/android/ui/activity/GenericExplanationActivity.java deleted file mode 100644 index 2921a838..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/activity/GenericExplanationActivity.java +++ /dev/null @@ -1,113 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.activity; - -import java.util.ArrayList; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.text.method.ScrollingMovementMethod; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ListAdapter; -import android.widget.ListView; -import android.widget.TextView; - -import com.actionbarsherlock.app.SherlockFragmentActivity; - -import de.mobilcom.debitel.cloud.android.R; - -/** - * Activity showing a text message and, optionally, a couple list of single or paired text strings. - * - * Added to show explanations for notifications when the user clicks on them, and there no place - * better to show them. - * - * @author David A. Velasco - */ -public class GenericExplanationActivity extends SherlockFragmentActivity { - - public static final String EXTRA_LIST = GenericExplanationActivity.class.getCanonicalName() + ".EXTRA_LIST"; - public static final String EXTRA_LIST_2 = GenericExplanationActivity.class.getCanonicalName() + ".EXTRA_LIST_2"; - public static final String MESSAGE = GenericExplanationActivity.class.getCanonicalName() + ".MESSAGE"; - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Intent intent = getIntent(); - String message = intent.getStringExtra(MESSAGE); - ArrayList list = intent.getStringArrayListExtra(EXTRA_LIST); - ArrayList list2 = intent.getStringArrayListExtra(EXTRA_LIST_2); - - setContentView(R.layout.generic_explanation); - - if (message != null) { - TextView textView = (TextView) findViewById(R.id.message); - textView.setText(message); - textView.setMovementMethod(new ScrollingMovementMethod()); - } - - ListView listView = (ListView) findViewById(R.id.list); - if (list != null && list.size() > 0) { - //ListAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, list); - ListAdapter adapter = new ExplanationListAdapterView(this, list, list2); - listView.setAdapter(adapter); - } else { - listView.setVisibility(View.GONE); - } - } - - public class ExplanationListAdapterView extends ArrayAdapter { - - ArrayList mList; - ArrayList mList2; - - ExplanationListAdapterView(Context context, ArrayList list, ArrayList list2) { - super(context, android.R.layout.two_line_list_item, android.R.id.text1, list); - mList = list; - mList2 = list2; - } - - @Override - public boolean isEnabled(int position) { - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public View getView (int position, View convertView, ViewGroup parent) { - View view = super.getView(position, convertView, parent); - if (view != null) { - if (mList2 != null && mList2.size() > 0 && position >= 0 && position < mList2.size()) { - TextView text2 = (TextView) view.findViewById(android.R.id.text2); - if (text2 != null) { - text2.setText(mList2.get(position)); - } - } - } - return view; - } - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/activity/InstantUploadActivity.java b/src/de/mobilcom/debitel/cloud/android/ui/activity/InstantUploadActivity.java deleted file mode 100644 index 23cb6f02..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/activity/InstantUploadActivity.java +++ /dev/null @@ -1,476 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.activity; - -import java.util.ArrayList; -import java.util.List; - -import android.accounts.Account; -import android.app.Activity; -import android.content.Intent; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.Bundle; -import android.util.SparseArray; -import android.view.Gravity; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnLongClickListener; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; -import de.mobilcom.debitel.cloud.android.db.DbHandler; -import de.mobilcom.debitel.cloud.android.files.InstantUploadBroadcastReceiver; -import de.mobilcom.debitel.cloud.android.files.services.FileUploader; -import de.mobilcom.debitel.cloud.android.ui.CustomButton; -import de.mobilcom.debitel.cloud.android.utils.FileStorageUtils; - -/** - * This Activity is used to display a list with images they could not be - * uploaded instantly. The images can be selected for delete or for a try again - * upload - * - * The entry-point for this activity is the 'Failed upload Notification" and a - * sub-menu underneath the 'Upload' menu-item - * - * @author andomaex / Matthias Baumann - */ -public class InstantUploadActivity extends Activity { - - private static final String LOG_TAG = InstantUploadActivity.class.getSimpleName(); - private LinearLayout listView; - private static final String retry_chexbox_tag = "retry_chexbox_tag"; - public static final boolean IS_ENABLED = false; - private static int MAX_LOAD_IMAGES = 5; - private int lastLoadImageIdx = 0; - - private SparseArray fileList = null; - CheckBox failed_upload_all_cb; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.failed_upload_files); - - CustomButton deleteAllBtn = (CustomButton) findViewById(R.id.failed_upload_delete_all_btn); - deleteAllBtn.setOnClickListener(getDeleteListner()); - CustomButton retryAllBtn = (CustomButton) findViewById(R.id.failed_upload_retry_all_btn); - retryAllBtn.setOnClickListener(getRetryListner()); - this.failed_upload_all_cb = (CheckBox) findViewById(R.id.failed_upload_headline_cb); - failed_upload_all_cb.setOnCheckedChangeListener(getCheckAllListener()); - listView = (LinearLayout) findViewById(R.id.failed_upload_scrollviewlayout); - - loadListView(true); - - } - - /** - * init the listview with ImageButtons, checkboxes and filename for every - * Image that was not successfully uploaded - * - * this method is call at Activity creation and on delete one ore more - * list-entry an on retry the upload by clicking the ImageButton or by click - * to the 'retry all' button - * - */ - private void loadListView(boolean reset) { - DbHandler db = new DbHandler(getApplicationContext()); - Cursor c = db.getFailedFiles(); - - if (reset) { - fileList = new SparseArray(); - listView.removeAllViews(); - lastLoadImageIdx = 0; - } - if (c != null) { - try { - c.moveToPosition(lastLoadImageIdx); - - while (c.moveToNext()) { - - lastLoadImageIdx++; - String imp_path = c.getString(1); - String message = c.getString(4); - fileList.put(lastLoadImageIdx, imp_path); - LinearLayout rowLayout = getHorizontalLinearLayout(lastLoadImageIdx); - rowLayout.addView(getFileCheckbox(lastLoadImageIdx)); - rowLayout.addView(getImageButton(imp_path, lastLoadImageIdx)); - rowLayout.addView(getFileButton(imp_path, message, lastLoadImageIdx)); - listView.addView(rowLayout); - Log_OC.d(LOG_TAG, imp_path + " on idx: " + lastLoadImageIdx); - if (lastLoadImageIdx % MAX_LOAD_IMAGES == 0) { - break; - } - } - if (lastLoadImageIdx > 0) { - addLoadMoreButton(listView); - } - } finally { - db.close(); - } - } - } - - private void addLoadMoreButton(LinearLayout listView) { - if (listView != null) { - Button loadmoreBtn = null; - View oldButton = listView.findViewById(42); - if (oldButton != null) { - // remove existing button - listView.removeView(oldButton); - // to add the button at the end - loadmoreBtn = (Button) oldButton; - } else { - // create a new button to add to the scoll view - loadmoreBtn = new Button(this); - loadmoreBtn.setId(42); - loadmoreBtn.setText(getString(R.string.failed_upload_load_more_images)); - loadmoreBtn.setBackgroundResource(R.color.background_color); - loadmoreBtn.setTextSize(12); - loadmoreBtn.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - loadListView(false); - } - - }); - } - listView.addView(loadmoreBtn); - } - } - - /** - * provide a list of CheckBox instances, looked up from parent listview this - * list ist used to select/deselect all checkboxes at the list - * - * @return List - */ - private List getCheckboxList() { - List list = new ArrayList(); - for (int i = 0; i < listView.getChildCount(); i++) { - Log_OC.d(LOG_TAG, "ListView has Childs: " + listView.getChildCount()); - View childView = listView.getChildAt(i); - if (childView != null && childView instanceof ViewGroup) { - View checkboxView = getChildViews((ViewGroup) childView); - if (checkboxView != null && checkboxView instanceof CheckBox) { - Log_OC.d(LOG_TAG, "found Child: " + checkboxView.getId() + " " + checkboxView.getClass()); - list.add((CheckBox) checkboxView); - } - } - } - return list; - } - - /** - * recursive called method, used from getCheckboxList method - * - * @param View - * @return View - */ - private View getChildViews(ViewGroup view) { - if (view != null) { - for (int i = 0; i < view.getChildCount(); i++) { - View cb = view.getChildAt(i); - if (cb != null && cb instanceof ViewGroup) { - return getChildViews((ViewGroup) cb); - } else if (cb instanceof CheckBox) { - return cb; - } - } - } - return null; - } - - /** - * create a new OnCheckedChangeListener for the 'check all' checkbox * - * - * @return OnCheckedChangeListener to select all checkboxes at the list - */ - private OnCheckedChangeListener getCheckAllListener() { - return new OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - List list = getCheckboxList(); - for (CheckBox checkbox : list) { - ((CheckBox) checkbox).setChecked(isChecked); - } - } - - }; - } - - /** - * Button click Listener for the retry button at the headline - * - * @return a Listener to perform a retry for all selected images - */ - private OnClickListener getRetryListner() { - return new OnClickListener() { - - @Override - public void onClick(View v) { - - try { - - List list = getCheckboxList(); - for (CheckBox checkbox : list) { - boolean to_retry = checkbox.isChecked(); - - Log_OC.d(LOG_TAG, "Checkbox for " + checkbox.getId() + " was checked: " + to_retry); - String img_path = fileList.get(checkbox.getId()); - if (to_retry) { - - final String msg = "Image-Path " + checkbox.getId() + " was checked: " + img_path; - Log_OC.d(LOG_TAG, msg); - startUpload(img_path); - } - - } - } finally { - // refresh the List - listView.removeAllViews(); - loadListView(true); - if (failed_upload_all_cb != null) { - failed_upload_all_cb.setChecked(false); - } - } - - } - }; - } - - /** - * Button click Listener for the delete button at the headline - * - * @return a Listener to perform a delete for all selected images - */ - private OnClickListener getDeleteListner() { - - return new OnClickListener() { - - @Override - public void onClick(View v) { - - final DbHandler dbh = new DbHandler(getApplicationContext()); - try { - List list = getCheckboxList(); - for (CheckBox checkbox : list) { - boolean to_be_delete = checkbox.isChecked(); - - Log_OC.d(LOG_TAG, "Checkbox for " + checkbox.getId() + " was checked: " + to_be_delete); - String img_path = fileList.get(checkbox.getId()); - Log_OC.d(LOG_TAG, "Image-Path " + checkbox.getId() + " was checked: " + img_path); - if (to_be_delete) { - boolean deleted = dbh.removeIUPendingFile(img_path); - Log_OC.d(LOG_TAG, "removing " + checkbox.getId() + " was : " + deleted); - - } - - } - } finally { - dbh.close(); - // refresh the List - listView.removeAllViews(); - loadListView(true); - if (failed_upload_all_cb != null) { - failed_upload_all_cb.setChecked(false); - } - } - - } - }; - } - - private LinearLayout getHorizontalLinearLayout(int id) { - LinearLayout linearLayout = new LinearLayout(getApplicationContext()); - linearLayout.setId(id); - linearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.MATCH_PARENT)); - linearLayout.setGravity(Gravity.RIGHT); - linearLayout.setOrientation(LinearLayout.HORIZONTAL); - return linearLayout; - } - - private LinearLayout getVerticalLinearLayout() { - LinearLayout linearLayout = new LinearLayout(getApplicationContext()); - linearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.MATCH_PARENT)); - linearLayout.setGravity(Gravity.TOP); - linearLayout.setOrientation(LinearLayout.VERTICAL); - return linearLayout; - } - - private View getFileButton(final String img_path, String message, int id) { - - TextView failureTextView = new TextView(this); - failureTextView.setText(getString(R.string.failed_upload_failure_text) + message); - failureTextView.setBackgroundResource(R.color.background_color); - failureTextView.setTextSize(8); - failureTextView.setOnLongClickListener(getOnLongClickListener(message)); - failureTextView.setPadding(5, 5, 5, 10); - TextView retryButton = new TextView(this); - retryButton.setId(id); - retryButton.setText(img_path); - retryButton.setBackgroundResource(R.color.background_color); - retryButton.setTextSize(8); - retryButton.setOnClickListener(getImageButtonOnClickListener(img_path)); - retryButton.setOnLongClickListener(getOnLongClickListener(message)); - retryButton.setPadding(5, 5, 5, 10); - LinearLayout verticalLayout = getVerticalLinearLayout(); - verticalLayout.addView(retryButton); - verticalLayout.addView(failureTextView); - - return verticalLayout; - } - - private OnLongClickListener getOnLongClickListener(final String message) { - return new OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - Log_OC.d(LOG_TAG, message); - Toast toast = Toast.makeText(InstantUploadActivity.this, getString(R.string.failed_upload_retry_text) - + message, Toast.LENGTH_LONG); - toast.show(); - return true; - } - - }; - } - - private CheckBox getFileCheckbox(int id) { - CheckBox retryCB = new CheckBox(this); - retryCB.setId(id); - retryCB.setBackgroundResource(R.color.background_color); - retryCB.setTextSize(8); - retryCB.setTag(retry_chexbox_tag); - return retryCB; - } - - private ImageButton getImageButton(String img_path, int id) { - ImageButton imageButton = new ImageButton(this); - imageButton.setId(id); - imageButton.setClickable(true); - imageButton.setOnClickListener(getImageButtonOnClickListener(img_path)); - - // scale and add a thumbnail to the imagebutton - int base_scale_size = 32; - if (img_path != null) { - Log_OC.d(LOG_TAG, "add " + img_path + " to Image Button"); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - Bitmap bitmap = BitmapFactory.decodeFile(img_path, options); - int width_tpm = options.outWidth, height_tmp = options.outHeight; - int scale = 3; - while (true) { - if (width_tpm / 2 < base_scale_size || height_tmp / 2 < base_scale_size) { - break; - } - width_tpm /= 2; - height_tmp /= 2; - scale++; - } - - Log_OC.d(LOG_TAG, "scale Imgae with: " + scale); - BitmapFactory.Options options2 = new BitmapFactory.Options(); - options2.inSampleSize = scale; - bitmap = BitmapFactory.decodeFile(img_path, options2); - - if (bitmap != null) { - Log_OC.d(LOG_TAG, "loaded Bitmap Bytes: " + bitmap.getRowBytes()); - imageButton.setImageBitmap(bitmap); - } else { - Log_OC.d(LOG_TAG, "could not load imgage: " + img_path); - } - } - return imageButton; - } - - private OnClickListener getImageButtonOnClickListener(final String img_path) { - return new OnClickListener() { - - @Override - public void onClick(View v) { - startUpload(img_path); - loadListView(true); - } - - }; - } - - /** - * start uploading a file to the INSTANT_UPLOD_DIR - * - * @param img_path - */ - private void startUpload(String img_path) { - // extract filename - String filename = FileStorageUtils.getInstantUploadFilePath(this, img_path); - if (canInstantUpload()) { - Account account = AccountUtils.getCurrentOwnCloudAccount(InstantUploadActivity.this); - // add file again to upload queue - DbHandler db = new DbHandler(InstantUploadActivity.this); - try { - db.updateFileState(img_path, DbHandler.UPLOAD_STATUS_UPLOAD_LATER, null); - } finally { - db.close(); - } - - Intent i = new Intent(InstantUploadActivity.this, FileUploader.class); - i.putExtra(FileUploader.KEY_ACCOUNT, account); - i.putExtra(FileUploader.KEY_LOCAL_FILE, img_path); - i.putExtra(FileUploader.KEY_REMOTE_FILE, filename); - i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE); - i.putExtra(de.mobilcom.debitel.cloud.android.files.services.FileUploader.KEY_INSTANT_UPLOAD, true); - - final String msg = "try to upload file with name :" + filename; - Log_OC.d(LOG_TAG, msg); - Toast toast = Toast.makeText(InstantUploadActivity.this, getString(R.string.failed_upload_retry_text) - + filename, Toast.LENGTH_LONG); - toast.show(); - - startService(i); - } else { - Toast toast = Toast.makeText(InstantUploadActivity.this, - getString(R.string.failed_upload_retry_do_nothing_text) + filename, Toast.LENGTH_LONG); - toast.show(); - } - } - - private boolean canInstantUpload() { - - if (!InstantUploadBroadcastReceiver.isOnline(this) - || (InstantUploadBroadcastReceiver.instantUploadViaWiFiOnly(this) && !InstantUploadBroadcastReceiver - .isConnectedViaWiFi(this))) { - return false; - } else { - return true; - } - } - -} \ No newline at end of file diff --git a/src/de/mobilcom/debitel/cloud/android/ui/activity/LandingActivity.java b/src/de/mobilcom/debitel/cloud/android/ui/activity/LandingActivity.java deleted file mode 100644 index 5d6b69f3..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/activity/LandingActivity.java +++ /dev/null @@ -1,158 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 Bartek Przybylski - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package de.mobilcom.debitel.cloud.android.ui.activity; - -import com.actionbarsherlock.app.SherlockFragmentActivity; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.GridView; -import android.widget.Toast; - -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.ui.adapter.LandingScreenAdapter; - -/** - * This activity is used as a landing page when the user first opens this app. - * - * @author Lennart Rosam - * - */ -public class LandingActivity extends SherlockFragmentActivity implements - OnClickListener, OnItemClickListener { - - public static final int DIALOG_SETUP_ACCOUNT = 1; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - - // Fill the grid view of the landing screen with icons - GridView landingScreenItems = (GridView) findViewById(R.id.homeScreenGrid); - landingScreenItems.setAdapter(new LandingScreenAdapter(this)); - landingScreenItems.setOnItemClickListener(this); - - // Check, if there are ownCloud accounts - if (!accountsAreSetup()) { - showDialog(DIALOG_SETUP_ACCOUNT); - } else { - // Start device tracking service - Intent locationServiceIntent = new Intent(); - locationServiceIntent - .setAction("de.mobilcom.debitel.cloud.android.location.LocationLauncher"); - sendBroadcast(locationServiceIntent); - } - - } - - @Override - protected void onRestart() { - super.onRestart(); - // Check, if there are ownCloud accounts - if (!accountsAreSetup()) { - showDialog(DIALOG_SETUP_ACCOUNT); - } - } - - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - // Check, if there are ownCloud accounts - if (!accountsAreSetup()) { - showDialog(DIALOG_SETUP_ACCOUNT); - } - } - - @Override - protected Dialog onCreateDialog(int id) { - Dialog dialog; - switch (id) { - case DIALOG_SETUP_ACCOUNT: - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.main_tit_accsetup); - builder.setMessage(R.string.main_wrn_accsetup); - builder.setCancelable(false); - builder.setPositiveButton(R.string.common_ok, this); - builder.setNegativeButton(R.string.common_cancel, this); - dialog = builder.create(); - break; - default: - dialog = null; - } - - return dialog; - } - - public void onClick(DialogInterface dialog, int which) { - // In any case - we won't need it anymore - dialog.dismiss(); - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT); - intent.putExtra("authorities", - new String[] { MainApp.getAuthTokenType() }); - startActivity(intent); - break; - case DialogInterface.BUTTON_NEGATIVE: - finish(); - } - - } - - @Override - /** - * Start an activity based on the selection - * the user made - */ - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Intent intent; - intent = (Intent) parent.getAdapter().getItem(position); - if (intent != null) { - startActivity(intent); - } else { - // TODO: Implement all of this and make this text go away ;-) - Toast toast = Toast.makeText(this, "Not yet implemented!", - Toast.LENGTH_SHORT); - toast.show(); - } - } - - /** - * Checks, whether or not there are any ownCloud accounts setup. - * - * @return true, if there is at least one account. - */ - private boolean accountsAreSetup() { - AccountManager accMan = AccountManager.get(this); - Account[] accounts = accMan - .getAccountsByType(MainApp.getAccountType()); - return accounts.length > 0; - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/activity/LogHistoryActivity.java b/src/de/mobilcom/debitel/cloud/android/ui/activity/LogHistoryActivity.java deleted file mode 100644 index 20317bc8..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/activity/LogHistoryActivity.java +++ /dev/null @@ -1,120 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.activity; - -import java.io.File; -import java.util.ArrayList; - -import android.content.Intent; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceChangeListener; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.ListView; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.SherlockPreferenceActivity; -import com.actionbarsherlock.view.MenuItem; - -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.ui.CustomButton; -import de.mobilcom.debitel.cloud.android.ui.adapter.LogListAdapter; -import de.mobilcom.debitel.cloud.android.utils.FileStorageUtils; - - - -public class LogHistoryActivity extends SherlockPreferenceActivity implements OnPreferenceChangeListener { - String logpath = FileStorageUtils.getLogPath(); - File logDIR = null; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.log_send_file); - setTitle("Log History"); - ActionBar actionBar = getSherlock().getActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); - ListView listView = (ListView) findViewById(android.R.id.list); - CustomButton deleteHistoryButton = (CustomButton) findViewById(R.id.deleteLogHistoryButton); - - deleteHistoryButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - File dir = new File(logpath); - if (dir != null) { - File[] files = dir.listFiles(); - if(files!=null) { - for(File f: files) { - f.delete(); - } - } - dir.delete(); - } - Intent intent = new Intent(getBaseContext(), Preferences.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - } - - }); - - - if(logpath != null){ - logDIR = new File(logpath); - } - - if(logDIR != null && logDIR.isDirectory()) { - File[] files = logDIR.listFiles(); - - if (files != null && files.length != 0) { - ArrayList logfiles_name = new ArrayList(); - for (File file : files) { - logfiles_name.add(file.getName()); - } - String[] logFiles2Array = logfiles_name.toArray(new String[logfiles_name.size()]); - LogListAdapter listadapter = new LogListAdapter(this,logFiles2Array); - listView.setAdapter(listadapter); - } - } - } - - - @Override - public boolean onMenuItemSelected(int featureId, MenuItem item) { - super.onMenuItemSelected(featureId, item); - Intent intent; - - switch (item.getItemId()) { - - case android.R.id.home: - intent = new Intent(getBaseContext(), Preferences.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - break; - default: - return false; - } - return true; - } - @Override - public boolean onPreferenceChange(Preference arg0, Object arg1) { - return false; - } -} \ No newline at end of file diff --git a/src/de/mobilcom/debitel/cloud/android/ui/activity/PinCodeActivity.java b/src/de/mobilcom/debitel/cloud/android/ui/activity/PinCodeActivity.java deleted file mode 100644 index 37e763d1..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/activity/PinCodeActivity.java +++ /dev/null @@ -1,638 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 Bartek Przybylski - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -package de.mobilcom.debitel.cloud.android.ui.activity; - -import java.util.Arrays; - -import com.actionbarsherlock.app.SherlockFragmentActivity; - -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.ui.CustomButton; - -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.KeyEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnFocusChangeListener; -import android.view.View.OnKeyListener; -import android.widget.EditText; -import android.widget.TextView; - -public class PinCodeActivity extends SherlockFragmentActivity { - - - public final static String EXTRA_ACTIVITY = "de.mobilcom.debitel.cloud.android.ui.activity.PinCodeActivity.ACTIVITY"; - public final static String EXTRA_NEW_STATE = "de.mobilcom.debitel.cloud.android.ui.activity.PinCodeActivity.NEW_STATE"; - - CustomButton bCancel; - TextView mPinHdr; - TextView mPinHdrExplanation; - EditText mText1; - EditText mText2; - EditText mText3; - EditText mText4; - - String [] tempText ={"","","",""}; - - String activity; - - boolean confirmingPinCode = false; - boolean pinCodeChecked = false; - boolean newPasswordEntered = false; - boolean bChange = true; // to control that only one blocks jump - int tCounter ; // Count the number of attempts an user could introduce the PIN code - - - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.pincodelock); - - Intent intent = getIntent(); - activity = intent.getStringExtra(EXTRA_ACTIVITY); - - bCancel = (CustomButton) findViewById(R.id.cancel); - mPinHdr = (TextView) findViewById(R.id.pinHdr); - mPinHdrExplanation = (TextView) findViewById(R.id.pinHdrExpl); - mText1 = (EditText) findViewById(R.id.txt1); - mText1.requestFocus(); - getWindow().setSoftInputMode(android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - mText2 = (EditText) findViewById(R.id.txt2); - mText3 = (EditText) findViewById(R.id.txt3); - mText4 = (EditText) findViewById(R.id.txt4); - - SharedPreferences appPrefs = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()); - - - // Not PIN Code defined yet. - // In a previous version settings is allow from start - if ( (appPrefs.getString("PrefPinCode1", null) == null ) ){ - setChangePincodeView(true); - pinCodeChecked = true; - newPasswordEntered = true; - - }else{ - - if (appPrefs.getBoolean("set_pincode", false)){ - // pincode activated - if (activity.equals("preferences")){ - // PIN has been activated yet - mPinHdr.setText(R.string.pincode_configure_your_pin); - mPinHdrExplanation.setVisibility(View.VISIBLE); - pinCodeChecked = true ; // No need to check it - setChangePincodeView(true); - }else{ - // PIN active - bCancel.setVisibility(View.INVISIBLE); - bCancel.setVisibility(View.GONE); - mPinHdr.setText(R.string.pincode_enter_pin_code); - mPinHdrExplanation.setVisibility(View.INVISIBLE); - setChangePincodeView(false); - } - - }else { - // pincode removal - mPinHdr.setText(R.string.pincode_remove_your_pincode); - mPinHdrExplanation.setVisibility(View.INVISIBLE); - pinCodeChecked = false; - setChangePincodeView(true); - } - - } - setTextListeners(); - - - } - - - - protected void setInitVars(){ - confirmingPinCode = false; - pinCodeChecked = false; - newPasswordEntered = false; - - } - - protected void setInitView(){ - bCancel.setVisibility(View.INVISIBLE); - bCancel.setVisibility(View.GONE); - mPinHdr.setText(R.string.pincode_enter_pin_code); - mPinHdrExplanation.setVisibility(View.INVISIBLE); - } - - - protected void setChangePincodeView(boolean state){ - - if(state){ - bCancel.setVisibility(View.VISIBLE); - bCancel.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - - SharedPreferences.Editor appPrefsE = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - - SharedPreferences appPrefs = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()); - - boolean state = appPrefs.getBoolean("set_pincode", false); - appPrefsE.putBoolean("set_pincode",!state); - appPrefsE.commit(); - setInitVars(); - finish(); - } - }); - } - - } - - - - /* - * - */ - protected void setTextListeners(){ - - /*------------------------------------------------ - * FIRST BOX - -------------------------------------------------*/ - - mText1.addTextChangedListener(new TextWatcher() { - - @Override - public void onTextChanged(CharSequence s, int start, int before, - int count) { - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, - int after) { - } - - @Override - public void afterTextChanged(Editable s) { - if (s.length() > 0) { - if (!confirmingPinCode){ - tempText[0] = mText1.getText().toString(); - - } - mText2.requestFocus(); - } - } - }); - - - - /*------------------------------------------------ - * SECOND BOX - -------------------------------------------------*/ - mText2.addTextChangedListener(new TextWatcher() { - - @Override - public void onTextChanged(CharSequence s, int start, int before, - int count) { - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, - int after) { - } - - @Override - public void afterTextChanged(Editable s) { - if (s.length() > 0) { - if (!confirmingPinCode){ - tempText[1] = mText2.getText().toString(); - } - - mText3.requestFocus(); - } - } - }); - - mText2.setOnKeyListener(new OnKeyListener() { - - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_DEL && bChange) { - - mText1.setText(""); - mText1.requestFocus(); - if (!confirmingPinCode) - tempText[0] = ""; - bChange= false; - - }else if(!bChange){ - bChange=true; - - } - return false; - } - }); - - mText2.setOnFocusChangeListener(new OnFocusChangeListener() { - - @Override - public void onFocusChange(View v, boolean hasFocus) { - mText2.setCursorVisible(true); - if (mText1.getText().toString().equals("")){ - mText2.setSelected(false); - mText2.setCursorVisible(false); - mText1.requestFocus(); - mText1.setSelected(true); - mText1.setSelection(0); - } - - } - }); - - - /*------------------------------------------------ - * THIRD BOX - -------------------------------------------------*/ - mText3.addTextChangedListener(new TextWatcher() { - - @Override - public void onTextChanged(CharSequence s, int start, int before, - int count) { - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, - int after) { - } - - @Override - public void afterTextChanged(Editable s) { - if (s.length() > 0) { - if (!confirmingPinCode){ - tempText[2] = mText3.getText().toString(); - } - mText4.requestFocus(); - } - } - }); - - mText3.setOnKeyListener(new OnKeyListener() { - - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_DEL && bChange) { - mText2.requestFocus(); - if (!confirmingPinCode) - tempText[1] = ""; - mText2.setText(""); - bChange= false; - - }else if(!bChange){ - bChange=true; - - } - return false; - } - }); - - mText3.setOnFocusChangeListener(new OnFocusChangeListener() { - - @Override - public void onFocusChange(View v, boolean hasFocus) { - mText3.setCursorVisible(true); - if (mText1.getText().toString().equals("")){ - mText3.setSelected(false); - mText3.setCursorVisible(false); - mText1.requestFocus(); - mText1.setSelected(true); - mText1.setSelection(0); - }else if (mText2.getText().toString().equals("")){ - mText3.setSelected(false); - mText3.setCursorVisible(false); - mText2.requestFocus(); - mText2.setSelected(true); - mText2.setSelection(0); - } - - } - }); - - /*------------------------------------------------ - * FOURTH BOX - -------------------------------------------------*/ - mText4.addTextChangedListener(new TextWatcher() { - - @Override - public void onTextChanged(CharSequence s, int start, int before, - int count) { - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, - int after) { - } - - @Override - public void afterTextChanged(Editable s) { - if (s.length() > 0) { - - if (!confirmingPinCode){ - tempText[3] = mText4.getText().toString(); - } - mText1.requestFocus(); - - if (!pinCodeChecked){ - pinCodeChecked = checkPincode(); - } - - if (pinCodeChecked && activity.equals("FileDisplayActivity")){ - finish(); - } else if (pinCodeChecked){ - - Intent intent = getIntent(); - String newState = intent.getStringExtra(EXTRA_NEW_STATE); - - if (newState.equals("false")){ - SharedPreferences.Editor appPrefs = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - appPrefs.putBoolean("set_pincode",false); - appPrefs.commit(); - - setInitVars(); - pinCodeEnd(false); - - }else{ - - if (!confirmingPinCode){ - pinCodeChangeRequest(); - - } else { - confirmPincode(); - } - } - - - } - } - } - }); - - - - mText4.setOnKeyListener(new OnKeyListener() { - - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_DEL && bChange) { - mText3.requestFocus(); - if (!confirmingPinCode) - tempText[2]=""; - mText3.setText(""); - bChange= false; - - }else if(!bChange){ - bChange=true; - } - return false; - } - }); - - mText4.setOnFocusChangeListener(new OnFocusChangeListener() { - - @Override - public void onFocusChange(View v, boolean hasFocus) { - mText4.setCursorVisible(true); - - if (mText1.getText().toString().equals("")){ - mText4.setSelected(false); - mText4.setCursorVisible(false); - mText1.requestFocus(); - mText1.setSelected(true); - mText1.setSelection(0); - }else if (mText2.getText().toString().equals("")){ - mText4.setSelected(false); - mText4.setCursorVisible(false); - mText2.requestFocus(); - mText2.setSelected(true); - mText2.setSelection(0); - }else if (mText3.getText().toString().equals("")){ - mText4.setSelected(false); - mText4.setCursorVisible(false); - mText3.requestFocus(); - mText3.setSelected(true); - mText3.setSelection(0); - } - - } - }); - - - - } // end setTextListener - - - protected void pinCodeChangeRequest(){ - - clearBoxes(); - mPinHdr.setText(R.string.pincode_reenter_your_pincode); - mPinHdrExplanation.setVisibility(View.INVISIBLE); - confirmingPinCode =true; - - } - - - protected boolean checkPincode(){ - - - SharedPreferences appPrefs = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()); - - String pText1 = appPrefs.getString("PrefPinCode1", null); - String pText2 = appPrefs.getString("PrefPinCode2", null); - String pText3 = appPrefs.getString("PrefPinCode3", null); - String pText4 = appPrefs.getString("PrefPinCode4", null); - - if ( tempText[0].equals(pText1) && - tempText[1].equals(pText2) && - tempText[2].equals(pText3) && - tempText[3].equals(pText4) ) { - - return true; - - - }else { - Arrays.fill(tempText, null); - AlertDialog aDialog = new AlertDialog.Builder(this).create(); - CharSequence errorSeq = getString(R.string.common_error); - aDialog.setTitle(errorSeq); - CharSequence cseq = getString(R.string.pincode_wrong); - aDialog.setMessage(cseq); - CharSequence okSeq = getString(R.string.common_ok); - aDialog.setButton(okSeq, new DialogInterface.OnClickListener(){ - - @Override - public void onClick(DialogInterface dialog, int which) { - return; - } - - }); - aDialog.show(); - clearBoxes(); - mPinHdr.setText(R.string.pincode_enter_pin_code); - mPinHdrExplanation.setVisibility(View.INVISIBLE); - newPasswordEntered = true; - confirmingPinCode = false; - - } - - - return false; - } - - protected void confirmPincode(){ - - confirmingPinCode = false; - - String rText1 = mText1.getText().toString(); - String rText2 = mText2.getText().toString(); - String rText3 = mText3.getText().toString(); - String rText4 = mText4.getText().toString(); - - if ( tempText[0].equals(rText1) && - tempText[1].equals(rText2) && - tempText[2].equals(rText3) && - tempText[3].equals(rText4) ) { - - savePincodeAndExit(); - - } else { - - Arrays.fill(tempText, null); - AlertDialog aDialog = new AlertDialog.Builder(this).create(); - CharSequence errorSeq = getString(R.string.common_error); - aDialog.setTitle(errorSeq); - CharSequence cseq = getString(R.string.pincode_mismatch); - aDialog.setMessage(cseq); - CharSequence okSeq = getString(R.string.common_ok); - aDialog.setButton(okSeq, new DialogInterface.OnClickListener(){ - - @Override - public void onClick(DialogInterface dialog, int which) { - return; - } - - }); - aDialog.show(); - mPinHdr.setText(R.string.pincode_configure_your_pin); - mPinHdrExplanation.setVisibility(View.VISIBLE); - clearBoxes(); - } - - } - - - protected void pinCodeEnd(boolean state){ - AlertDialog aDialog = new AlertDialog.Builder(this).create(); - - if (state){ - CharSequence saveSeq = getString(R.string.common_save_exit); - aDialog.setTitle(saveSeq); - CharSequence cseq = getString(R.string.pincode_stored); - aDialog.setMessage(cseq); - - }else{ - CharSequence saveSeq = getString(R.string.common_save_exit); - aDialog.setTitle(saveSeq); - CharSequence cseq = getString(R.string.pincode_removed); - aDialog.setMessage(cseq); - - } - CharSequence okSeq = getString(R.string.common_ok); - aDialog.setButton(okSeq, new DialogInterface.OnClickListener(){ - - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - return; - } - - }); - aDialog.show(); - } - - protected void savePincodeAndExit(){ - SharedPreferences.Editor appPrefs = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - - appPrefs.putString("PrefPinCode1", tempText[0]); - appPrefs.putString("PrefPinCode2",tempText[1]); - appPrefs.putString("PrefPinCode3", tempText[2]); - appPrefs.putString("PrefPinCode4", tempText[3]); - appPrefs.putBoolean("set_pincode",true); - appPrefs.commit(); - - pinCodeEnd(true); - - - - } - - - protected void clearBoxes(){ - - mText1.setText(""); - mText2.setText(""); - mText3.setText(""); - mText4.setText(""); - mText1.requestFocus(); - } - - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event){ - if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount()== 0){ - - if (activity.equals("preferences")){ - SharedPreferences.Editor appPrefsE = PreferenceManager - - .getDefaultSharedPreferences(getApplicationContext()).edit(); - - SharedPreferences appPrefs = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()); - - boolean state = appPrefs.getBoolean("set_pincode", false); - appPrefsE.putBoolean("set_pincode",!state); - appPrefsE.commit(); - setInitVars(); - finish(); - } - return true; - - } - - return super.onKeyDown(keyCode, event); - } - - - - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/activity/Preferences.java b/src/de/mobilcom/debitel/cloud/android/ui/activity/Preferences.java deleted file mode 100644 index 7b34a96d..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/activity/Preferences.java +++ /dev/null @@ -1,359 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.ui.activity; - -import java.util.Vector; - -import android.accounts.Account; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.Uri; -import android.os.Bundle; -import android.preference.CheckBoxPreference; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceChangeListener; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.PreferenceCategory; -import android.preference.PreferenceManager; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.SherlockPreferenceActivity; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuItem; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.OwnCloudSession; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; -import de.mobilcom.debitel.cloud.android.db.DbHandler; - -/** - * An Activity that allows the user to change the application's settings. - * - * @author Bartek Przybylski - * - */ -public class Preferences extends SherlockPreferenceActivity implements OnPreferenceChangeListener { - - private static final String TAG = "OwnCloudPreferences"; - private final int mNewSession = 47; - private final int mEditSession = 48; - private DbHandler mDbHandler; - private Vector mSessions; - private ListPreference mTrackingUpdateInterval; - private CheckBoxPreference mDeviceTracking; - private CheckBoxPreference pCode; - //private CheckBoxPreference pLogging; - //private Preference pLoggingHistory; - private Preference pAboutApp; - private int mSelectedMenuItem; - - - @SuppressWarnings("deprecation") - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mDbHandler = new DbHandler(getBaseContext()); - mSessions = new Vector(); - addPreferencesFromResource(R.xml.preferences); - //populateAccountList(); - ActionBar actionBar = getSherlock().getActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); - - Preference p = findPreference("manage_account"); - if (p != null) - p.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - Intent i = new Intent(getApplicationContext(), AccountSelectActivity.class); - startActivity(i); - return true; - } - }); - - pCode = (CheckBoxPreference) findPreference("set_pincode"); - if (pCode != null){ - pCode.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - Intent i = new Intent(getApplicationContext(), PinCodeActivity.class); - i.putExtra(PinCodeActivity.EXTRA_ACTIVITY, "preferences"); - i.putExtra(PinCodeActivity.EXTRA_NEW_STATE, newValue.toString()); - startActivity(i); - - return true; - } - }); - - } - - - - PreferenceCategory preferenceCategory = (PreferenceCategory) findPreference("more"); - - boolean helpEnabled = getResources().getBoolean(R.bool.help_enabled); - Preference pHelp = findPreference("help"); - if (pHelp != null ){ - if (helpEnabled) { - pHelp.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - String helpWeb =(String) getText(R.string.url_help); - if (helpWeb != null && helpWeb.length() > 0) { - Uri uriUrl = Uri.parse(helpWeb); - Intent intent = new Intent(Intent.ACTION_VIEW, uriUrl); - startActivity(intent); - } - return true; - } - }); - } else { - preferenceCategory.removePreference(pHelp); - } - - } - - - boolean recommendEnabled = getResources().getBoolean(R.bool.recommend_enabled); - Preference pRecommend = findPreference("recommend"); - if (pRecommend != null){ - if (recommendEnabled) { - pRecommend.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - - Intent intent = new Intent(Intent.ACTION_SENDTO); - intent.setType("text/plain"); - Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(Preferences.this); - String appName = getString(R.string.app_name); - String username = currentAccount.name.substring(0, currentAccount.name.lastIndexOf('@')); - String recommendSubject = String.format(getString(R.string.recommend_subject), username, appName); - //String recommendSubject = String.format(getString(R.string.recommend_subject), appName); - intent.putExtra(Intent.EXTRA_SUBJECT, recommendSubject); - String recommendText = String.format(getString(R.string.recommend_text), getString(R.string.app_name), username); - //String recommendText = String.format(getString(R.string.recommend_text), getString(R.string.app_name), getString(R.string.url_app_download)); - intent.putExtra(Intent.EXTRA_TEXT, recommendText); - - intent.setData(Uri.parse(getString(R.string.mail_recommend))); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - - - return(true); - - } - }); - } else { - preferenceCategory.removePreference(pRecommend); - } - - } - - boolean feedbackEnabled = getResources().getBoolean(R.bool.feedback_enabled); - Preference pFeedback = findPreference("feedback"); - if (pFeedback != null){ - if (feedbackEnabled) { - pFeedback.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - String feedbackMail =(String) getText(R.string.mail_feedback); - String feedback =(String) getText(R.string.prefs_feedback); - Intent intent = new Intent(Intent.ACTION_SENDTO); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_SUBJECT, feedback); - - intent.setData(Uri.parse(feedbackMail)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - - return true; - } - }); - } else { - preferenceCategory.removePreference(pFeedback); - } - - } - - boolean imprintEnabled = getResources().getBoolean(R.bool.imprint_enabled); - Preference pImprint = findPreference("imprint"); - if (pImprint != null) { - if (imprintEnabled) { - pImprint.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - String imprintWeb = (String) getText(R.string.url_imprint); - if (imprintWeb != null && imprintWeb.length() > 0) { - Uri uriUrl = Uri.parse(imprintWeb); - Intent intent = new Intent(Intent.ACTION_VIEW, uriUrl); - startActivity(intent); - } - //ImprintDialog.newInstance(true).show(preference.get, "IMPRINT_DIALOG"); - return true; - } - }); - } else { - preferenceCategory.removePreference(pImprint); - } - } - - /* About App */ - pAboutApp = (Preference) findPreference("about_app"); - if (pAboutApp != null) { - pAboutApp.setTitle(String.format(getString(R.string.about_android), getString(R.string.app_name))); - PackageInfo pkg; - try { - pkg = getPackageManager().getPackageInfo(getPackageName(), 0); - pAboutApp.setSummary(String.format(getString(R.string.about_version), pkg.versionName)); - } catch (NameNotFoundException e) { - Log_OC.e(TAG, "Error while showing about dialog", e); - } - } - - /* DISABLED FOR RELEASE UNTIL FIXED - pLogging = (CheckBoxPreference) findPreference("log_to_file"); - if (pLogging != null) { - pLogging.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - - String logpath = Environment.getExternalStorageDirectory()+File.separator+"owncloud"+File.separator+"log"; - - if(!pLogging.isChecked()) { - Log_OC.d("Debug", "start logging"); - Log_OC.v("PATH", logpath); - Log_OC.startLogging(logpath); - } - else { - Log_OC.d("Debug", "stop logging"); - Log_OC.stopLogging(); - } - return true; - } - }); - } - - pLoggingHistory = (Preference) findPreference("log_history"); - if (pLoggingHistory != null) { - pLoggingHistory.setOnPreferenceClickListener(new OnPreferenceClickListener() { - - @Override - public boolean onPreferenceClick(Preference preference) { - Intent intent = new Intent(getApplicationContext(),LogHistoryActivity.class); - startActivity(intent); - return true; - } - }); - } - */ - - } - - @Override - protected void onResume() { - SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - boolean state = appPrefs.getBoolean("set_pincode", false); - pCode.setChecked(state); - super.onResume(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - return true; - } - - @Override - public boolean onMenuItemSelected(int featureId, MenuItem item) { - super.onMenuItemSelected(featureId, item); - Intent intent; - - switch (item.getItemId()) { - //case R.id.addSessionItem: - case 1: - intent = new Intent(this, PreferencesNewSession.class); - startActivityForResult(intent, mNewSession); - break; - case R.id.SessionContextEdit: - intent = new Intent(this, PreferencesNewSession.class); - intent.putExtra("sessionId", mSessions.get(mSelectedMenuItem) - .getEntryId()); - intent.putExtra("sessionName", mSessions.get(mSelectedMenuItem) - .getName()); - intent.putExtra("sessionURL", mSessions.get(mSelectedMenuItem) - .getUrl()); - startActivityForResult(intent, mEditSession); - break; - case android.R.id.home: - intent = new Intent(getBaseContext(), FileDisplayActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - break; - default: - Log_OC.w(TAG, "Unknown menu item triggered"); - return false; - } - return true; - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - protected void onDestroy() { - mDbHandler.close(); - super.onDestroy(); - } - - @Override - /** - * Updates various summaries after updates. Also starts and stops - * the - */ - public boolean onPreferenceChange(Preference preference, Object newValue) { - // Update current account summary - /*if (preference.equals(mAccountList)) { - mAccountList.setSummary(newValue.toString()); - } - - // Update tracking interval summary - else*/ if (preference.equals(mTrackingUpdateInterval)) { - String trackingSummary = getResources().getString( - R.string.prefs_trackmydevice_interval_summary); - trackingSummary = String.format(trackingSummary, - newValue.toString()); - mTrackingUpdateInterval.setSummary(trackingSummary); - } - - // Start or stop tracking service - else if (preference.equals(mDeviceTracking)) { - Intent locationServiceIntent = new Intent(); - locationServiceIntent - .setAction("de.mobilcom.debitel.cloud.android.location.LocationLauncher"); - locationServiceIntent.putExtra("TRACKING_SETTING", - (Boolean) newValue); - sendBroadcast(locationServiceIntent); - } - return true; - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/activity/PreferencesNewSession.java b/src/de/mobilcom/debitel/cloud/android/ui/activity/PreferencesNewSession.java deleted file mode 100644 index d78b1e95..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/activity/PreferencesNewSession.java +++ /dev/null @@ -1,116 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.ui.activity; - -import android.accounts.AccountAuthenticatorActivity; -import android.app.Activity; -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; - -public class PreferencesNewSession extends AccountAuthenticatorActivity - implements OnClickListener { - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // setContentView(R.layout.add_new_session); - /* - * EditText et;// = (EditText) - * findViewById(R.id.newSession_sessionName); - * - * et = (EditText) findViewById(R.id.newSession_URL); if - * (getIntent().hasExtra("sessionURL")) { try { URI uri = new - * URI(getIntent().getStringExtra("sessionURL")); String url = - * uri.getHost(); if (uri.getPort() != -1) { url += ":" + - * String.valueOf(uri.getPort()); } if (uri.getPath() != null) { url += - * uri.getPath(); } else { url += "/"; } et.setText(url); et = - * (EditText) findViewById(R.id.newSession_username); if - * (uri.getAuthority() != null) { if (uri.getUserInfo().indexOf(':') != - * -1) { et.setText(uri.getUserInfo().substring(0, - * uri.getUserInfo().indexOf(':'))); et = (EditText) - * findViewById(R.id.newSession_password); - * et.setText(uri.getUserInfo().substring - * (uri.getUserInfo().indexOf(':')+1)); } else { - * et.setText(uri.getUserInfo()); } } - * - * } catch (URISyntaxException e) { Log.e(TAG, "Incorrect URI syntax " + - * e.getLocalizedMessage()); } } - * - * mReturnData = new Intent(); setResult(Activity.RESULT_OK, - * mReturnData); ((Button) - * findViewById(R.id.button1)).setOnClickListener(this); ((Button) - * findViewById(R.id.button2)).setOnClickListener(this); - */ - } - - @Override - protected void onResume() { - super.onResume(); - } - - public void onClick(View v) { - /* - * switch (v.getId()) { case R.id.button1: Intent intent = new Intent(); - * if (getIntent().hasExtra("sessionId")) { intent.putExtra("sessionId", - * getIntent().getIntExtra("sessionId", -1)); } //String sessionName = - * ((EditText) - * findViewById(R.id.newSession_sessionName)).getText().toString(); // - * if (sessionName.trim().equals("") || !isNameValid(sessionName)) { // - * Toast.makeText(this, R.string.new_session_session_name_error, - * Toast.LENGTH_LONG).show(); // break; // } URI uri = prepareURI(); if - * (uri != null) { //intent.putExtra("sessionName", sessionName); - * intent.putExtra("sessionURL", uri.toString()); - * setResult(Activity.RESULT_OK, intent); AccountManager accMgr = - * AccountManager.get(this); Account a = new Account("OwnCloud", - * AccountAuthenticatorService.ACCOUNT_TYPE); - * accMgr.addAccountExplicitly(a, "asd", null); finish(); } break; case - * R.id.button2: setResult(Activity.RESULT_CANCELED); finish(); break; } - */ - } - - /* - * private URI prepareURI() { URI uri = null; String url = ""; try { String - * username = ((EditText) - * findViewById(R.id.newSession_username)).getText().toString().trim(); - * String password = ((EditText) - * findViewById(R.id.newSession_password)).getText().toString().trim(); - * String hostname = ((EditText) - * findViewById(R.id.newSession_URL)).getText().toString().trim(); String - * scheme; if (hostname.matches("[A-Za-z]://")) { scheme = - * hostname.substring(0, hostname.indexOf("://")+3); hostname = - * hostname.substring(hostname.indexOf("://")+3); } else { scheme = - * "http://"; } if (!username.equals("")) { if (!password.equals("")) { - * username += ":" + password + "@"; } else { username += "@"; } } url = - * scheme + username + hostname; Log.i(TAG, url); uri = new URI(url); } - * catch (URISyntaxException e) { Log.e(TAG, "Incorrect URI syntax " + - * e.getLocalizedMessage()); Toast.makeText(this, - * R.string.new_session_uri_error, Toast.LENGTH_LONG).show(); } return uri; - * } - * - * private boolean isNameValid(String string) { return - * string.matches("[A-Za-z0-9 _-]*"); } - */ - - @Override - public void onBackPressed() { - setResult(Activity.RESULT_CANCELED); - super.onBackPressed(); - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/activity/TransferServiceGetter.java b/src/de/mobilcom/debitel/cloud/android/ui/activity/TransferServiceGetter.java deleted file mode 100644 index acf17e53..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/activity/TransferServiceGetter.java +++ /dev/null @@ -1,42 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.ui.activity; - -import de.mobilcom.debitel.cloud.android.files.services.FileDownloader.FileDownloaderBinder; -import de.mobilcom.debitel.cloud.android.files.services.FileUploader.FileUploaderBinder; - -public interface TransferServiceGetter { - - /** - * Callback method invoked when the parent activity is fully created to get a reference to the FileDownloader service API. - * - * @return Directory to list firstly. Can be NULL. - */ - public FileDownloaderBinder getFileDownloaderBinder(); - - - /** - * Callback method invoked when the parent activity is fully created to get a reference to the FileUploader service API. - * - * @return Directory to list firstly. Can be NULL. - */ - public FileUploaderBinder getFileUploaderBinder(); - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/activity/UploadFilesActivity.java b/src/de/mobilcom/debitel/cloud/android/ui/activity/UploadFilesActivity.java deleted file mode 100644 index 6d0c88d6..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/activity/UploadFilesActivity.java +++ /dev/null @@ -1,395 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.activity; - -import java.io.File; - -import android.accounts.Account; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Environment; -import android.support.v4.app.DialogFragment; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.TextView; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.ActionBar.OnNavigationListener; -import com.actionbarsherlock.view.MenuItem; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.ui.CustomButton; -import de.mobilcom.debitel.cloud.android.ui.dialog.IndeterminateProgressDialog; -import de.mobilcom.debitel.cloud.android.ui.fragment.ConfirmationDialogFragment; -import de.mobilcom.debitel.cloud.android.ui.fragment.LocalFileListFragment; -import de.mobilcom.debitel.cloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener; -import de.mobilcom.debitel.cloud.android.utils.FileStorageUtils; - -/** - * Displays local files and let the user choose what of them wants to upload - * to the current ownCloud account - * - * @author David A. Velasco - * - */ - -public class UploadFilesActivity extends FileActivity implements - LocalFileListFragment.ContainerActivity, OnNavigationListener, OnClickListener, ConfirmationDialogFragmentListener { - - private ArrayAdapter mDirectories; - private File mCurrentDir = null; - private LocalFileListFragment mFileListFragment; - private CustomButton mCancelBtn; - private CustomButton mUploadBtn; - private Account mAccountOnCreation; - private DialogFragment mCurrentDialog; - - public static final String EXTRA_CHOSEN_FILES = UploadFilesActivity.class.getCanonicalName() + ".EXTRA_CHOSEN_FILES"; - - public static final int RESULT_OK_AND_MOVE = RESULT_FIRST_USER; - - private static final String KEY_DIRECTORY_PATH = UploadFilesActivity.class.getCanonicalName() + ".KEY_DIRECTORY_PATH"; - private static final String TAG = "UploadFilesActivity"; - private static final String WAIT_DIALOG_TAG = "WAIT"; - private static final String QUERY_TO_MOVE_DIALOG_TAG = "QUERY_TO_MOVE"; - - - @Override - public void onCreate(Bundle savedInstanceState) { - Log_OC.d(TAG, "onCreate() start"); - super.onCreate(savedInstanceState); - - if(savedInstanceState != null) { - mCurrentDir = new File(savedInstanceState.getString(UploadFilesActivity.KEY_DIRECTORY_PATH)); - } else { - mCurrentDir = Environment.getExternalStorageDirectory(); - } - - mAccountOnCreation = getAccount(); - - /// USER INTERFACE - - // Drop-down navigation - mDirectories = new CustomArrayAdapter(this, R.layout.sherlock_spinner_dropdown_item); - File currDir = mCurrentDir; - while(currDir != null && currDir.getParentFile() != null) { - mDirectories.add(currDir.getName()); - currDir = currDir.getParentFile(); - } - mDirectories.add(File.separator); - - // Inflate and set the layout view - setContentView(R.layout.upload_files_layout); - mFileListFragment = (LocalFileListFragment) getSupportFragmentManager().findFragmentById(R.id.local_files_list); - - - // Set input controllers - mCancelBtn = (CustomButton) findViewById(R.id.upload_files_btn_cancel); - mCancelBtn.setOnClickListener(this); - mUploadBtn = (CustomButton) findViewById(R.id.upload_files_btn_upload); - mUploadBtn.setOnClickListener(this); - - - // Action bar setup - ActionBar actionBar = getSupportActionBar(); - actionBar.setHomeButtonEnabled(true); // mandatory since Android ICS, according to the official documentation - actionBar.setDisplayHomeAsUpEnabled(mCurrentDir != null && mCurrentDir.getName() != null); - actionBar.setDisplayShowTitleEnabled(false); - actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); - actionBar.setListNavigationCallbacks(mDirectories, this); - - // wait dialog - if (mCurrentDialog != null) { - mCurrentDialog.dismiss(); - mCurrentDialog = null; - } - - Log_OC.d(TAG, "onCreate() end"); - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - boolean retval = true; - switch (item.getItemId()) { - case android.R.id.home: { - if(mCurrentDir != null && mCurrentDir.getParentFile() != null){ - onBackPressed(); - } - break; - } - default: - retval = super.onOptionsItemSelected(item); - } - return retval; - } - - - @Override - public boolean onNavigationItemSelected(int itemPosition, long itemId) { - int i = itemPosition; - while (i-- != 0) { - onBackPressed(); - } - // the next operation triggers a new call to this method, but it's necessary to - // ensure that the name exposed in the action bar is the current directory when the - // user selected it in the navigation list - if (itemPosition != 0) - getSupportActionBar().setSelectedNavigationItem(0); - return true; - } - - - @Override - public void onBackPressed() { - if (mDirectories.getCount() <= 1) { - finish(); - return; - } - popDirname(); - mFileListFragment.onNavigateUp(); - mCurrentDir = mFileListFragment.getCurrentDirectory(); - - if(mCurrentDir.getParentFile() == null){ - ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayHomeAsUpEnabled(false); - } - } - - - @Override - protected void onSaveInstanceState(Bundle outState) { - // responsibility of restore is preferred in onCreate() before than in onRestoreInstanceState when there are Fragments involved - Log_OC.d(TAG, "onSaveInstanceState() start"); - super.onSaveInstanceState(outState); - outState.putString(UploadFilesActivity.KEY_DIRECTORY_PATH, mCurrentDir.getAbsolutePath()); - Log_OC.d(TAG, "onSaveInstanceState() end"); - } - - - /** - * Pushes a directory to the drop down list - * @param directory to push - * @throws IllegalArgumentException If the {@link File#isDirectory()} returns false. - */ - public void pushDirname(File directory) { - if(!directory.isDirectory()){ - throw new IllegalArgumentException("Only directories may be pushed!"); - } - mDirectories.insert(directory.getName(), 0); - mCurrentDir = directory; - } - - /** - * Pops a directory name from the drop down list - * @return True, unless the stack is empty - */ - public boolean popDirname() { - mDirectories.remove(mDirectories.getItem(0)); - return !mDirectories.isEmpty(); - } - - - // Custom array adapter to override text colors - private class CustomArrayAdapter extends ArrayAdapter { - - public CustomArrayAdapter(UploadFilesActivity ctx, int view) { - super(ctx, view); - } - - public View getView(int position, View convertView, ViewGroup parent) { - View v = super.getView(position, convertView, parent); - - ((TextView) v).setTextColor(getResources().getColorStateList( - android.R.color.white)); - return v; - } - - public View getDropDownView(int position, View convertView, - ViewGroup parent) { - View v = super.getDropDownView(position, convertView, parent); - - ((TextView) v).setTextColor(getResources().getColorStateList( - android.R.color.white)); - - return v; - } - - } - - /** - * {@inheritDoc} - */ - @Override - public void onDirectoryClick(File directory) { - pushDirname(directory); - ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); - } - - - /** - * {@inheritDoc} - */ - @Override - public void onFileClick(File file) { - // nothing to do - } - - /** - * {@inheritDoc} - */ - @Override - public File getInitialDirectory() { - return mCurrentDir; - } - - - /** - * Performs corresponding action when user presses 'Cancel' or 'Upload' button - * - * TODO Make here the real request to the Upload service ; will require to receive the account and - * target folder where the upload must be done in the received intent. - */ - @Override - public void onClick(View v) { - if (v.getId() == R.id.upload_files_btn_cancel) { - setResult(RESULT_CANCELED); - finish(); - - } else if (v.getId() == R.id.upload_files_btn_upload) { - new CheckAvailableSpaceTask().execute(); - } - } - - - /** - * Asynchronous task checking if there is space enough to copy all the files chosen - * to upload into the ownCloud local folder. - * - * Maybe an AsyncTask is not strictly necessary, but who really knows. - * - * @author David A. Velasco - */ - private class CheckAvailableSpaceTask extends AsyncTask { - - /** - * Updates the UI before trying the movement - */ - @Override - protected void onPreExecute () { - /// progress dialog and disable 'Move' button - mCurrentDialog = IndeterminateProgressDialog.newInstance(R.string.wait_a_moment, false); - mCurrentDialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG); - } - - - /** - * Checks the available space - * - * @return 'True' if there is space enough. - */ - @Override - protected Boolean doInBackground(Void... params) { - String[] checkedFilePaths = mFileListFragment.getCheckedFilePaths(); - long total = 0; - for (int i=0; checkedFilePaths != null && i < checkedFilePaths.length ; i++) { - String localPath = checkedFilePaths[i]; - File localFile = new File(localPath); - total += localFile.length(); - } - return (FileStorageUtils.getUsableSpace(mAccountOnCreation.name) >= total); - } - - /** - * 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. - * - * @param result 'True' when there is space enough to copy all the selected files. - */ - @Override - protected void onPostExecute(Boolean result) { - mCurrentDialog.dismiss(); - mCurrentDialog = null; - - if (result) { - // return the list of selected files (success) - Intent data = new Intent(); - data.putExtra(EXTRA_CHOSEN_FILES, mFileListFragment.getCheckedFilePaths()); - setResult(RESULT_OK, data); - finish(); - - } else { - // show a dialog to query the user if wants to move the selected files 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); - dialog.setOnConfirmationListener(UploadFilesActivity.this); - dialog.show(getSupportFragmentManager(), QUERY_TO_MOVE_DIALOG_TAG); - } - } - } - - @Override - public void onConfirmation(String callerTag) { - Log_OC.d(TAG, "Positive button in dialog was clicked; dialog tag is " + callerTag); - if (callerTag.equals(QUERY_TO_MOVE_DIALOG_TAG)) { - // return the list of selected files to the caller activity (success), signaling that they should be moved to the ownCloud folder, instead of copied - Intent data = new Intent(); - data.putExtra(EXTRA_CHOSEN_FILES, mFileListFragment.getCheckedFilePaths()); - setResult(RESULT_OK_AND_MOVE, data); - finish(); - } - } - - - @Override - public void onNeutral(String callerTag) { - Log_OC.d(TAG, "Phantom neutral button in dialog was clicked; dialog tag is " + callerTag); - } - - - @Override - public void onCancel(String callerTag) { - /// nothing to do; don't finish, let the user change the selection - Log_OC.d(TAG, "Negative button in dialog was clicked; dialog tag is " + callerTag); - } - - - @Override - protected void onAccountSet(boolean stateWasRecovered) { - if (getAccount() != null) { - if (!mAccountOnCreation.equals(getAccount())) { - setResult(RESULT_CANCELED); - finish(); - } - - } else { - Log_OC.wtf(TAG, "onAccountChanged was called with NULL account associated!"); - setResult(RESULT_CANCELED); - finish(); - } - } - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/adapter/FileListListAdapter.java b/src/de/mobilcom/debitel/cloud/android/ui/adapter/FileListListAdapter.java deleted file mode 100644 index 5ab922c3..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/adapter/FileListListAdapter.java +++ /dev/null @@ -1,209 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.ui.adapter; - -import android.accounts.Account; -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.ListView; -import android.widget.TextView; - -import de.mobilcom.debitel.cloud.android.DisplayUtils; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; -import de.mobilcom.debitel.cloud.android.datamodel.DataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.files.services.FileDownloader.FileDownloaderBinder; -import de.mobilcom.debitel.cloud.android.files.services.FileUploader.FileUploaderBinder; -import de.mobilcom.debitel.cloud.android.ui.activity.TransferServiceGetter; - -import java.util.Vector; - - -/** - * This Adapter populates a ListView with all files and folders in an ownCloud - * instance. - * - * @author Bartek Przybylski - * - */ -public class FileListListAdapter extends BaseAdapter implements ListAdapter { - private Context mContext; - private OCFile mFile = null; - private Vector mFiles = null; - private DataStorageManager mStorageManager; - private Account mAccount; - private TransferServiceGetter mTransferServiceGetter; - - public FileListListAdapter(Context context, TransferServiceGetter transferServiceGetter) { - mContext = context; - mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext); - mTransferServiceGetter = transferServiceGetter; - } - - @Override - public boolean areAllItemsEnabled() { - return true; - } - - @Override - public boolean isEnabled(int position) { - return true; - } - - @Override - public int getCount() { - return mFiles != null ? mFiles.size() : 0; - } - - @Override - public Object getItem(int position) { - if (mFiles == null || mFiles.size() <= position) - return null; - return mFiles.get(position); - } - - @Override - public long getItemId(int position) { - if (mFiles == null || mFiles.size() <= position) - return 0; - return mFiles.get(position).getFileId(); - } - - @Override - public int getItemViewType(int position) { - return 0; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View view = convertView; - if (view == null) { - LayoutInflater inflator = (LayoutInflater) mContext - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - view = inflator.inflate(R.layout.list_item, null); - } - - if (mFiles != null && mFiles.size() > position) { - OCFile file = mFiles.get(position); - TextView fileName = (TextView) view.findViewById(R.id.Filename); - String name = file.getFileName(); - - fileName.setText(name); - ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1); - fileIcon.setImageResource(DisplayUtils.getResourceId(file.getMimetype())); - ImageView localStateView = (ImageView) view.findViewById(R.id.imageView2); - FileDownloaderBinder downloaderBinder = mTransferServiceGetter.getFileDownloaderBinder(); - FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder(); - if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) { - localStateView.setImageResource(R.drawable.downloading_file_indicator); - localStateView.setVisibility(View.VISIBLE); - } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file)) { - localStateView.setImageResource(R.drawable.uploading_file_indicator); - localStateView.setVisibility(View.VISIBLE); - } else if (file.isDown()) { - localStateView.setImageResource(R.drawable.local_file_indicator); - localStateView.setVisibility(View.VISIBLE); - } else { - localStateView.setVisibility(View.INVISIBLE); - } - - TextView fileSizeV = (TextView) view.findViewById(R.id.file_size); - TextView lastModV = (TextView) view.findViewById(R.id.last_mod); - ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox); - - if (!file.isDirectory()) { - fileSizeV.setVisibility(View.VISIBLE); - fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength())); - lastModV.setVisibility(View.VISIBLE); - lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.getModificationTimestamp())); - // this if-else is needed even thoe fav icon is visible by default - // because android reuses views in listview - if (!file.keepInSync()) { - view.findViewById(R.id.imageView3).setVisibility(View.GONE); - } else { - view.findViewById(R.id.imageView3).setVisibility(View.VISIBLE); - } - - ListView parentList = (ListView)parent; - if (parentList.getChoiceMode() == ListView.CHOICE_MODE_NONE) { - checkBoxV.setVisibility(View.GONE); - } else { - if (parentList.isItemChecked(position)) { - checkBoxV.setImageResource(android.R.drawable.checkbox_on_background); - } else { - checkBoxV.setImageResource(android.R.drawable.checkbox_off_background); - } - checkBoxV.setVisibility(View.VISIBLE); - } - - } - else { - - fileSizeV.setVisibility(View.VISIBLE); - fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength())); - lastModV.setVisibility(View.VISIBLE); - lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.getModificationTimestamp())); - checkBoxV.setVisibility(View.GONE); - view.findViewById(R.id.imageView3).setVisibility(View.GONE); - } - } - - return view; - } - - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public boolean isEmpty() { - return (mFiles == null || mFiles.isEmpty()); - } - - /** - * Change the adapted directory for a new one - * @param directory New file to adapt. Can be NULL, meaning "no content to adapt". - * @param updatedStorageManager Optional updated storage manager; used to replace mStorageManager if is different (and not NULL) - */ - public void swapDirectory(OCFile directory, DataStorageManager updatedStorageManager) { - mFile = directory; - if (updatedStorageManager != null && updatedStorageManager != mStorageManager) { - mStorageManager = updatedStorageManager; - mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext); - } - if (mStorageManager != null) { - mFiles = mStorageManager.getDirectoryContent(mFile); - } else { - mFiles = null; - } - notifyDataSetChanged(); - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/adapter/LandingScreenAdapter.java b/src/de/mobilcom/debitel/cloud/android/ui/adapter/LandingScreenAdapter.java deleted file mode 100644 index 42491f85..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/adapter/LandingScreenAdapter.java +++ /dev/null @@ -1,112 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.ui.adapter; - - -import android.content.Context; -import android.content.Intent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; -import de.mobilcom.debitel.cloud.android.ui.activity.FileDisplayActivity; -import de.mobilcom.debitel.cloud.android.ui.activity.Preferences; - -/** - * Populates the landing screen icons. - * - * @author Lennart Rosam - * - */ -public class LandingScreenAdapter extends BaseAdapter { - - private Context mContext; - - private final Integer[] mLandingScreenIcons = { R.drawable.home, - R.drawable.music, R.drawable.contacts, R.drawable.calendar, - android.R.drawable.ic_menu_agenda, R.drawable.settings }; - - private final Integer[] mLandingScreenTexts = { R.string.main_files, - R.string.main_music, R.string.main_contacts, - R.string.main_calendar, R.string.main_bookmarks, - R.string.main_settings }; - - public LandingScreenAdapter(Context context) { - mContext = context; - } - - @Override - public int getCount() { - return mLandingScreenIcons.length; - } - - @Override - /** - * Returns the Intent associated with this object - * or null if the functionality is not yet implemented - */ - public Object getItem(int position) { - Intent intent = new Intent(); - - switch (position) { - case 0: - /* - * The FileDisplayActivity requires the ownCloud account as an - * parcableExtra. We will put in the one that is selected in the - * preferences - */ - intent.setClass(mContext, FileDisplayActivity.class); - intent.putExtra("ACCOUNT", - AccountUtils.getCurrentOwnCloudAccount(mContext)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - break; - case 5: - intent.setClass(mContext, Preferences.class); - break; - default: - intent = null; - } - return intent; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - LayoutInflater inflator = LayoutInflater.from(mContext); - convertView = inflator.inflate(R.layout.landing_page_item, null); - - ImageView icon = (ImageView) convertView - .findViewById(R.id.gridImage); - TextView iconText = (TextView) convertView - .findViewById(R.id.gridText); - - icon.setImageResource(mLandingScreenIcons[position]); - iconText.setText(mLandingScreenTexts[position]); - } - return convertView; - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/adapter/LocalFileListAdapter.java b/src/de/mobilcom/debitel/cloud/android/ui/adapter/LocalFileListAdapter.java deleted file mode 100644 index 9b375774..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/adapter/LocalFileListAdapter.java +++ /dev/null @@ -1,184 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.ui.adapter; - -import java.io.File; -import java.util.Arrays; -import java.util.Comparator; - -import de.mobilcom.debitel.cloud.android.DisplayUtils; -import de.mobilcom.debitel.cloud.android.R; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.ListView; -import android.widget.TextView; - -/** - * This Adapter populates a ListView with all files and directories contained - * in a local directory - * - * @author David A. Velasco - * - */ -public class LocalFileListAdapter extends BaseAdapter implements ListAdapter { - - private Context mContext; - private File mDirectory; - private File[] mFiles = null; - - public LocalFileListAdapter(File directory, Context context) { - mContext = context; - swapDirectory(directory); - } - - @Override - public boolean areAllItemsEnabled() { - return true; - } - - @Override - public boolean isEnabled(int position) { - return true; - } - - @Override - public int getCount() { - return mFiles != null ? mFiles.length : 0; - } - - @Override - public Object getItem(int position) { - if (mFiles == null || mFiles.length <= position) - return null; - return mFiles[position]; - } - - @Override - public long getItemId(int position) { - return mFiles != null && mFiles.length <= position ? position : -1; - } - - @Override - public int getItemViewType(int position) { - return 0; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View view = convertView; - if (view == null) { - LayoutInflater inflator = (LayoutInflater) mContext - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - view = inflator.inflate(R.layout.list_item, null); - } - if (mFiles != null && mFiles.length > position) { - File file = mFiles[position]; - - TextView fileName = (TextView) view.findViewById(R.id.Filename); - String name = file.getName(); - fileName.setText(name); - - ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1); - if (!file.isDirectory()) { - fileIcon.setImageResource(R.drawable.file); - } else { - fileIcon.setImageResource(R.drawable.ic_menu_archive); - } - - TextView fileSizeV = (TextView) view.findViewById(R.id.file_size); - TextView lastModV = (TextView) view.findViewById(R.id.last_mod); - ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox); - if (!file.isDirectory()) { - fileSizeV.setVisibility(View.VISIBLE); - fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.length())); - lastModV.setVisibility(View.VISIBLE); - lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.lastModified())); - ListView parentList = (ListView)parent; - if (parentList.getChoiceMode() == ListView.CHOICE_MODE_NONE) { - checkBoxV.setVisibility(View.GONE); - } else { - if (parentList.isItemChecked(position)) { - checkBoxV.setImageResource(android.R.drawable.checkbox_on_background); - } else { - checkBoxV.setImageResource(android.R.drawable.checkbox_off_background); - } - checkBoxV.setVisibility(View.VISIBLE); - } - - } else { - fileSizeV.setVisibility(View.GONE); - lastModV.setVisibility(View.GONE); - checkBoxV.setVisibility(View.GONE); - } - - view.findViewById(R.id.imageView2).setVisibility(View.INVISIBLE); // not GONE; the alignment changes; ugly way to keep it - view.findViewById(R.id.imageView3).setVisibility(View.GONE); - } - - return view; - } - - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public boolean hasStableIds() { - return false; - } - - @Override - public boolean isEmpty() { - return (mFiles == null || mFiles.length == 0); - } - - /** - * Change the adapted directory for a new one - * @param directory New file to adapt. Can be NULL, meaning "no content to adapt". - */ - public void swapDirectory(File directory) { - mDirectory = directory; - mFiles = (mDirectory != null ? mDirectory.listFiles() : null); - if (mFiles != null) { - Arrays.sort(mFiles, new Comparator() { - @Override - public int compare(File lhs, File rhs) { - if (lhs.isDirectory() && !rhs.isDirectory()) { - return -1; - } else if (!lhs.isDirectory() && rhs.isDirectory()) { - return 1; - } - return compareNames(lhs, rhs); - } - - private int compareNames(File lhs, File rhs) { - return lhs.getName().toLowerCase().compareTo(rhs.getName().toLowerCase()); - } - - }); - } - notifyDataSetChanged(); - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/adapter/LogListAdapter.java b/src/de/mobilcom/debitel/cloud/android/ui/adapter/LogListAdapter.java deleted file mode 100644 index a0a90829..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/adapter/LogListAdapter.java +++ /dev/null @@ -1,54 +0,0 @@ -package de.mobilcom.debitel.cloud.android.ui.adapter; - -import java.io.File; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Environment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.TextView; - -import de.mobilcom.debitel.cloud.android.R; - - -public class LogListAdapter extends ArrayAdapter { - private Context context = null; - private String[] values; - private Uri fileUri = null; - - - public LogListAdapter(Context context, String[] values) { - super(context, R.layout.log_item, values); - this.context = context; - this.values = values; - } - - @Override - public View getView(final int position, View convertView, ViewGroup parent) { - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View rowView = inflater.inflate(R.layout.log_item, parent, false); - TextView listText = (TextView) rowView.findViewById(R.id.log_item_single); - listText.setText(values[position]); - listText.setTextSize(15); - fileUri = Uri.fromFile(new File(Environment.getExternalStorageDirectory()+File.separator+"owncloud"+File.separator+"log"+File.separator+values[position])); - listText.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND); - emailIntent.setType("text/rtf"); - emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "OwnCloud Logfile"); - emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, "This is a automatic E-mail send by owncloud/android"); - emailIntent.putExtra(android.content.Intent.EXTRA_STREAM, fileUri); - emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(Intent.createChooser(emailIntent, "Send mail...")); - } - }); - return rowView; - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/dialog/ChangelogDialog.java b/src/de/mobilcom/debitel/cloud/android/ui/dialog/ChangelogDialog.java deleted file mode 100644 index 0ba97d99..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/dialog/ChangelogDialog.java +++ /dev/null @@ -1,104 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.dialog; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.webkit.WebView; - -import com.actionbarsherlock.app.SherlockDialogFragment; - -import de.mobilcom.debitel.cloud.android.R; - -/** - * Dialog to show the contents of res/raw/CHANGELOG.txt - */ -public class ChangelogDialog extends SherlockDialogFragment { - - private static final String ARG_CANCELABLE = ChangelogDialog.class.getCanonicalName() + ".ARG_CANCELABLE"; - - - /** - * Public factory method to get dialog instances. - * - * @param cancelable If 'true', the dialog can be cancelled by the user input (BACK button, touch outside...) - * @return New dialog instance, ready to show. - */ - public static ChangelogDialog newInstance(boolean cancelable) { - ChangelogDialog fragment = new ChangelogDialog(); - Bundle args = new Bundle(); - args.putBoolean(ARG_CANCELABLE, cancelable); - fragment.setArguments(args); - return fragment; - } - - - /** - * {@inheritDoc} - */ - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - /// load the custom view to insert in the dialog, between title and - WebView webview = new WebView(getActivity()); - webview.loadUrl("file:///android_res/raw/" + getResources().getResourceEntryName(R.raw.changelog) + ".html"); - - /// build the dialog - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - Dialog dialog = builder.setView(webview) - .setIcon(R.drawable.icon) - //.setTitle(R.string.whats_new) - .setPositiveButton(R.string.common_ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }).create(); - - dialog.setCancelable(getArguments().getBoolean(ARG_CANCELABLE)); - return dialog; - } - - /** - * {@inheritDoc} - *-/ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - /// load the custom layout - View view = inflater.inflate(R.layout.fragment_changelog, container); - mEditText = (EditText) view.findViewById(R.id.txt_your_name); - getDialog().setTitle(R.string.whats_new); - - /// read full contents of the change log file (don't make it too big) - InputStream changeLogStream = getResources().openRawResource(R.raw.changelog); - Scanner scanner = new java.util.Scanner(changeLogStream).useDelimiter("\\A"); - String text = scanner.hasNext() ? scanner.next() : ""; - - /// make clickable the links in the change log file - SpannableString sText = new SpannableString(text); - Linkify.addLinks(sText, Linkify.ALL); - - return view; - } - */ -} - - diff --git a/src/de/mobilcom/debitel/cloud/android/ui/dialog/ConflictsResolveDialog.java b/src/de/mobilcom/debitel/cloud/android/ui/dialog/ConflictsResolveDialog.java deleted file mode 100644 index 7d7bc4ad..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/dialog/ConflictsResolveDialog.java +++ /dev/null @@ -1,121 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.ui.dialog; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; - -import com.actionbarsherlock.app.SherlockDialogFragment; -import com.actionbarsherlock.app.SherlockFragmentActivity; - -import de.mobilcom.debitel.cloud.android.R; - -/** - * Dialog which will be displayed to user upon keep-in-sync file conflict. - * - * @author Bartek Przybylski - * - */ -public class ConflictsResolveDialog extends SherlockDialogFragment { - - public static enum Decision { - CANCEL, - KEEP_BOTH, - OVERWRITE - } - - OnConflictDecisionMadeListener mListener; - - public static ConflictsResolveDialog newInstance(String path, OnConflictDecisionMadeListener listener) { - ConflictsResolveDialog f = new ConflictsResolveDialog(); - Bundle args = new Bundle(); - args.putString("remotepath", path); - f.setArguments(args); - f.mListener = listener; - return f; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - String remotepath = getArguments().getString("remotepath"); - return new AlertDialog.Builder(getSherlockActivity()) - .setIcon(R.drawable.icon) - .setTitle(R.string.conflict_title) - .setMessage(String.format(getString(R.string.conflict_message), remotepath)) - .setPositiveButton(R.string.conflict_overwrite, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - if (mListener != null) - mListener.ConflictDecisionMade(Decision.OVERWRITE); - } - }) - .setNeutralButton(R.string.conflict_keep_both, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (mListener != null) - mListener.ConflictDecisionMade(Decision.KEEP_BOTH); - } - }) - .setNegativeButton(R.string.conflict_dont_upload, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (mListener != null) - mListener.ConflictDecisionMade(Decision.CANCEL); - } - }) - .create(); - } - - public void showDialog(SherlockFragmentActivity activity) { - Fragment prev = activity.getSupportFragmentManager().findFragmentByTag("dialog"); - FragmentTransaction ft = activity.getSupportFragmentManager().beginTransaction(); - if (prev != null) { - ft.remove(prev); - } - ft.addToBackStack(null); - - this.show(ft, "dialog"); - } - - public void dismissDialog(SherlockFragmentActivity activity) { - Fragment prev = activity.getSupportFragmentManager().findFragmentByTag(getTag()); - if (prev != null) { - FragmentTransaction ft = activity.getSupportFragmentManager().beginTransaction(); - ft.remove(prev); - ft.commit(); - } - } - - @Override - public void onCancel(DialogInterface dialog) { - mListener.ConflictDecisionMade(Decision.CANCEL); - } - - public interface OnConflictDecisionMadeListener { - public void ConflictDecisionMade(Decision decision); - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/dialog/EditNameDialog.java b/src/de/mobilcom/debitel/cloud/android/ui/dialog/EditNameDialog.java deleted file mode 100644 index e3240464..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/dialog/EditNameDialog.java +++ /dev/null @@ -1,173 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.ui.dialog; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager.LayoutParams; -import android.widget.EditText; -import android.widget.TextView; - -import com.actionbarsherlock.app.SherlockDialogFragment; - -import de.mobilcom.debitel.cloud.android.R; - - -/** - * Dialog to request the user to input a name, optionally initialized with a former name. - * - * @author Bartek Przybylski - * @author David A. Velasco - */ -public class EditNameDialog extends SherlockDialogFragment implements DialogInterface.OnClickListener { - - public static final String TAG = EditNameDialog.class.getSimpleName(); - - protected static final String ARG_TITLE = "TITLE"; - protected static final String ARG_NAME = "NAME"; - protected static final String ARG_SELECTION_START = "SELECTION_START"; - protected static final String ARG_SELECTION_END = "SELECTION_END"; - - private String mNewFilename; - private boolean mResult; - private EditNameDialogListener mListener; - - /** - * Public factory method to get dialog instances. - * - * @param title Text to show as title in the dialog. - * @param name Optional text to include in the text input field when the dialog is shown. - * @param listener Instance to notify when the dialog is dismissed. - * @param selectionStart Index to the first character to be selected in the input field; negative value for none - * @param selectionEnd Index to the last character to be selected in the input field; negative value for none - * @return New dialog instance, ready to show. - */ - static public EditNameDialog newInstance(String title, String name, int selectionStart, int selectionEnd, EditNameDialogListener listener) { - EditNameDialog f = new EditNameDialog(); - Bundle args = new Bundle(); - args.putString(ARG_TITLE, title); - args.putString(ARG_NAME, name); - args.putInt(ARG_SELECTION_START, selectionStart); - args.putInt(ARG_SELECTION_END, selectionEnd); - f.setArguments(args); - f.setOnDismissListener(listener); - return f; - } - - - /** - * {@inheritDoc} - */ - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - String currentName = getArguments().getString(ARG_NAME); - if (currentName == null) - currentName = ""; - String title = getArguments().getString(ARG_TITLE); - - // Inflate the layout for the dialog - LayoutInflater inflater = getSherlockActivity().getLayoutInflater(); - View v = inflater.inflate(R.layout.edit_box_dialog, null); // null parent view because it will go in the dialog layout - EditText inputText = ((EditText)v.findViewById(R.id.user_input)); - inputText.setText(currentName); - - // Set it to the dialog - AlertDialog.Builder builder = new AlertDialog.Builder(getSherlockActivity()); - builder.setView(v) - .setPositiveButton(R.string.common_ok, this) - .setNegativeButton(R.string.common_cancel, this); - - if (title != null) { - builder.setTitle(title); - } - - mResult = false; - - Dialog d = builder.create(); - - inputText.requestFocus(); - int selectionStart = getArguments().getInt(ARG_SELECTION_START, -1); - int selectionEnd = getArguments().getInt(ARG_SELECTION_END, -1); - if (selectionStart >= 0 && selectionEnd >= 0) { - inputText.setSelection(Math.min(selectionStart, selectionEnd), Math.max(selectionStart, selectionEnd)); - } - d.getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE); - return d; - } - - - /** - * Performs the corresponding action when a dialog button is clicked. - * - * Saves the text in the input field to be accessed through {@link #getNewFilename()} when the positive - * button is clicked. - * - * Notify the current listener in any case. - */ - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case AlertDialog.BUTTON_POSITIVE: { - mNewFilename = ((TextView)(getDialog().findViewById(R.id.user_input))).getText().toString(); - mResult = true; - } - case AlertDialog.BUTTON_NEGATIVE: { // fall through - dismiss(); - if (mListener != null) - mListener.onDismiss(this); - } - } - } - - protected void setOnDismissListener(EditNameDialogListener listener) { - mListener = listener; - } - - /** - * Returns the text in the input field after the user clicked the positive button. - * - * @return Text in the input field. - */ - public String getNewFilename() { - return mNewFilename; - } - - /** - * - * @return True when the user clicked the positive button. - */ - public boolean getResult() { - return mResult; - } - - - /** - * Interface to receive a notification when any button in the dialog is clicked. - */ - public interface EditNameDialogListener { - public void onDismiss(EditNameDialog dialog); - } - - -} - diff --git a/src/de/mobilcom/debitel/cloud/android/ui/dialog/IndeterminateProgressDialog.java b/src/de/mobilcom/debitel/cloud/android/ui/dialog/IndeterminateProgressDialog.java deleted file mode 100644 index b44c04b9..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/dialog/IndeterminateProgressDialog.java +++ /dev/null @@ -1,92 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.dialog; - -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.DialogInterface; -import android.content.DialogInterface.OnKeyListener; -import android.os.Bundle; -import android.view.KeyEvent; - -import com.actionbarsherlock.app.SherlockDialogFragment; - -import de.mobilcom.debitel.cloud.android.R; - -public class IndeterminateProgressDialog extends SherlockDialogFragment { - - private static final String ARG_MESSAGE_ID = IndeterminateProgressDialog.class.getCanonicalName() + ".ARG_MESSAGE_ID"; - private static final String ARG_CANCELABLE = IndeterminateProgressDialog.class.getCanonicalName() + ".ARG_CANCELABLE"; - - - /** - * Public factory method to get dialog instances. - * - * @param messageId Resource id for a message to show in the dialog. - * @param cancelable If 'true', the dialog can be cancelled by the user input (BACK button, touch outside...) - * @return New dialog instance, ready to show. - */ - public static IndeterminateProgressDialog newInstance(int messageId, boolean cancelable) { - IndeterminateProgressDialog fragment = new IndeterminateProgressDialog(); - Bundle args = new Bundle(); - args.putInt(ARG_MESSAGE_ID, messageId); - args.putBoolean(ARG_CANCELABLE, cancelable); - fragment.setArguments(args); - return fragment; - } - - - /** - * {@inheritDoc} - */ - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - /// create indeterminate progress dialog - final ProgressDialog dialog = new ProgressDialog(getActivity()); - dialog.setIndeterminate(true); - - /// set message - int messageId = getArguments().getInt(ARG_MESSAGE_ID, R.string.placeholder_sentence); - dialog.setMessage(getString(messageId)); - - /// set cancellation behavior - boolean cancelable = getArguments().getBoolean(ARG_CANCELABLE, false); - if (!cancelable) { - dialog.setCancelable(false); - // disable the back button - OnKeyListener keyListener = new OnKeyListener() { - @Override - public boolean onKey(DialogInterface dialog, int keyCode, - KeyEvent event) { - - if( keyCode == KeyEvent.KEYCODE_BACK){ - return true; - } - return false; - } - - }; - dialog.setOnKeyListener(keyListener); - } - - return dialog; - } - -} - - diff --git a/src/de/mobilcom/debitel/cloud/android/ui/dialog/LoadingDialog.java b/src/de/mobilcom/debitel/cloud/android/ui/dialog/LoadingDialog.java deleted file mode 100644 index 7394cec3..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/dialog/LoadingDialog.java +++ /dev/null @@ -1,58 +0,0 @@ -package de.mobilcom.debitel.cloud.android.ui.dialog; - -import de.mobilcom.debitel.cloud.android.R; - -import android.app.Dialog; -import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.widget.TextView; - -public class LoadingDialog extends DialogFragment { - - private String mMessage; - - public LoadingDialog() { - super(); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setRetainInstance(true); - setCancelable(false); - } - - public LoadingDialog(String message) { - this.mMessage = message; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Create a view by inflating desired layout - View v = inflater.inflate(R.layout.loading_dialog, container, false); - - // set value - TextView tv = (TextView) v.findViewById(R.id.loadingText); - tv.setText(mMessage); - - return v; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - Dialog dialog = super.onCreateDialog(savedInstanceState); - dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); - return dialog; - } - - @Override - public void onDestroyView() { - if (getDialog() != null && getRetainInstance()) - getDialog().setDismissMessage(null); - super.onDestroyView(); - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/dialog/SamlWebViewDialog.java b/src/de/mobilcom/debitel/cloud/android/ui/dialog/SamlWebViewDialog.java deleted file mode 100644 index d897b606..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/dialog/SamlWebViewDialog.java +++ /dev/null @@ -1,285 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.dialog; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.os.Handler; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.app.FragmentManager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.CookieManager; -import android.webkit.CookieSyncManager; -import android.webkit.WebBackForwardList; -import android.webkit.WebSettings; -import android.webkit.WebView; - -import com.actionbarsherlock.app.SherlockDialogFragment; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.authentication.SsoWebViewClient; -import de.mobilcom.debitel.cloud.android.authentication.SsoWebViewClient.SsoWebViewClientListener; - -import eu.alefzero.webdav.WebdavClient; - -/** - * Dialog to show the WebView for SAML Authentication - * - * @author Maria Asensio - * @author David A. Velasco - */ -public class SamlWebViewDialog extends SherlockDialogFragment { - - public final String SAML_DIALOG_TAG = "SamlWebViewDialog"; - - private final static String TAG = SamlWebViewDialog.class.getSimpleName(); - - private static final String ARG_INITIAL_URL = "INITIAL_URL"; - private static final String ARG_TARGET_URL = "TARGET_URL"; - private static final String KEY_WEBVIEW_STATE = "WEBVIEW_STATE"; - - private WebView mSsoWebView; - private SsoWebViewClient mWebViewClient; - - private String mInitialUrl; - private String mTargetUrl; - - private Handler mHandler; - - private SsoWebViewClientListener mSsoWebViewClientListener; - - //private View mSsoRootView; - - - /** - * Public factory method to get dialog instances. - * - * @param handler - * @param Url Url to open at WebView - * @param targetURL mHostBaseUrl + AccountUtils.getWebdavPath(mDiscoveredVersion, mCurrentAuthTokenType) - * @return New dialog instance, ready to show. - */ - public static SamlWebViewDialog newInstance(String url, String targetUrl) { - Log_OC.d(TAG, "New instance"); - SamlWebViewDialog fragment = new SamlWebViewDialog(); - Bundle args = new Bundle(); - args.putString(ARG_INITIAL_URL, url); - args.putString(ARG_TARGET_URL, targetUrl); - fragment.setArguments(args); - return fragment; - } - - - public SamlWebViewDialog() { - super(); - Log_OC.d(TAG, "constructor"); - } - - - @Override - public void onAttach(Activity activity) { - Log_OC.d(TAG, "onAttach"); - super.onAttach(activity); - try { - mSsoWebViewClientListener = (SsoWebViewClientListener) activity; - mHandler = new Handler(); - mWebViewClient = new SsoWebViewClient(mHandler, mSsoWebViewClientListener); - - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() + " must implement " + SsoWebViewClientListener.class.getSimpleName()); - } - } - - - @SuppressLint("SetJavaScriptEnabled") - @Override - public void onCreate(Bundle savedInstanceState) { - Log_OC.d(TAG, "onCreate"); - super.onCreate(savedInstanceState); - - CookieSyncManager.createInstance(getActivity()); - - if (savedInstanceState == null) { - mInitialUrl = getArguments().getString(ARG_INITIAL_URL); - mTargetUrl = getArguments().getString(ARG_TARGET_URL); - } else { - mInitialUrl = savedInstanceState.getString(ARG_INITIAL_URL); - mTargetUrl = savedInstanceState.getString(ARG_TARGET_URL); - } - - setStyle(SherlockDialogFragment.STYLE_NO_TITLE, R.style.Theme_ownCloud_Dialog); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - Log_OC.d(TAG, "onCreateDialog"); - - /* - // build the dialog - AlertDialog.Builder builder = new AlertDialog.Builder(getSherlockActivity()); - if (mSsoRootView.getParent() != null) { - ((ViewGroup)(mSsoRootView.getParent())).removeView(mSsoRootView); - } - builder.setView(mSsoRootView); - //builder.setView(mSsoWebView); - Dialog dialog = builder.create(); - */ - - return super.onCreateDialog(savedInstanceState); - } - - @SuppressLint("SetJavaScriptEnabled") - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - Log_OC.d(TAG, "onCreateView"); - - // Inflate layout of the dialog - View rootView = inflater.inflate(R.layout.sso_dialog, container, false); // null parent view because it will go in the dialog layout - mSsoWebView = (WebView) rootView.findViewById(R.id.sso_webview); - - mWebViewClient.setTargetUrl(mTargetUrl); - mSsoWebView.setWebViewClient(mWebViewClient); - - if (savedInstanceState == null) { - Log_OC.d(TAG, " initWebView start"); - CookieManager cookieManager = CookieManager.getInstance(); - cookieManager.setAcceptCookie(true); - cookieManager.removeAllCookie(); - mSsoWebView.loadUrl(mInitialUrl); - - } else { - Log_OC.d(TAG, " restoreWebView start"); - WebBackForwardList history = mSsoWebView.restoreState(savedInstanceState.getBundle(KEY_WEBVIEW_STATE)); - if (history == null) { - Log_OC.e(TAG, "Error restoring WebView state ; back to starting URL"); - mSsoWebView.loadUrl(mInitialUrl); - } - } - - WebSettings webSettings = mSsoWebView.getSettings(); - webSettings.setJavaScriptEnabled(true); - webSettings.setBuiltInZoomControls(true); - webSettings.setLoadWithOverviewMode(false); - webSettings.setSavePassword(false); - webSettings.setUserAgentString(WebdavClient.USER_AGENT); - webSettings.setSaveFormData(false); - - return rootView; - } - - @Override - public void onSaveInstanceState(Bundle outState) { - Log_OC.d(SAML_DIALOG_TAG, "onSaveInstanceState being CALLED"); - super.onSaveInstanceState(outState); - - // save URLs - outState.putString(ARG_INITIAL_URL, mInitialUrl); - outState.putString(ARG_TARGET_URL, mTargetUrl); - - // Save the state of the WebView - Bundle webviewState = new Bundle(); - mSsoWebView.saveState(webviewState); - outState.putBundle(KEY_WEBVIEW_STATE, webviewState); - } - - @Override - public void onDestroyView() { - Log_OC.d(TAG, "onDestroyView"); - - mSsoWebView.setWebViewClient(null); - - // Work around bug: http://code.google.com/p/android/issues/detail?id=17423 - Dialog dialog = getDialog(); - if ((dialog != null)) { - dialog.setOnDismissListener(null); - //dialog.dismiss(); - //dialog.setDismissMessage(null); - } - - super.onDestroyView(); - } - - @Override - public void onDestroy() { - Log_OC.d(TAG, "onDestroy"); - super.onDestroy(); - } - - @Override - public void onDetach() { - Log_OC.d(TAG, "onDetach"); - mSsoWebViewClientListener = null; - mWebViewClient = null; - super.onDetach(); - } - - @Override - public void onCancel (DialogInterface dialog) { - Log_OC.d(SAML_DIALOG_TAG, "onCancel"); - super.onCancel(dialog); - } - - @Override - public void onDismiss (DialogInterface dialog) { - Log_OC.d(SAML_DIALOG_TAG, "onDismiss"); - super.onDismiss(dialog); - } - - @Override - public void onStart() { - Log_OC.d(SAML_DIALOG_TAG, "onStart"); - super.onStart(); - } - - @Override - public void onStop() { - Log_OC.d(SAML_DIALOG_TAG, "onStop"); - super.onStop(); - } - - @Override - public void onResume() { - Log_OC.d(SAML_DIALOG_TAG, "onResume"); - super.onResume(); - } - - @Override - public void onPause() { - Log_OC.d(SAML_DIALOG_TAG, "onPause"); - super.onPause(); - } - - @Override - public int show (FragmentTransaction transaction, String tag) { - Log_OC.d(SAML_DIALOG_TAG, "show (transaction)"); - return super.show(transaction, tag); - } - - @Override - public void show (FragmentManager manager, String tag) { - Log_OC.d(SAML_DIALOG_TAG, "show (manager)"); - super.show(manager, tag); - } - -} \ No newline at end of file diff --git a/src/de/mobilcom/debitel/cloud/android/ui/dialog/SslValidatorDialog.java b/src/de/mobilcom/debitel/cloud/android/ui/dialog/SslValidatorDialog.java deleted file mode 100644 index f75b9842..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/dialog/SslValidatorDialog.java +++ /dev/null @@ -1,355 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.dialog; - -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import javax.security.auth.x500.X500Principal; - -import android.app.Dialog; -import android.content.Context; -import android.os.Bundle; -import android.view.View; -import android.view.Window; -import android.widget.TextView; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.network.CertificateCombinedException; -import de.mobilcom.debitel.cloud.android.network.OwnCloudClientUtils; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult; -import de.mobilcom.debitel.cloud.android.ui.CustomButton; - -/** - * Dialog to request the user about a certificate that could not be validated with the certificates store in the system. - * - * @author David A. Velasco - */ -public class SslValidatorDialog extends Dialog { - - private final static String TAG = SslValidatorDialog.class.getSimpleName(); - - private OnSslValidatorListener mListener; - private CertificateCombinedException mException = null; - private View mView; - - - /** - * Creates a new SslValidatorDialog to ask the user if an untrusted certificate from a server should - * be trusted. - * - * @param context Android context where the dialog will live. - * @param result Result of a failed remote operation. - * @param listener Object to notice when the server certificate was added to the local certificates store. - * @return A new SslValidatorDialog instance. NULL if the operation can not be recovered - * by setting the certificate as reliable. - */ - public static SslValidatorDialog newInstance(Context context, RemoteOperationResult result, OnSslValidatorListener listener) { - if (result != null && result.isSslRecoverableException()) { - SslValidatorDialog dialog = new SslValidatorDialog(context, listener); - return dialog; - } else { - return null; - } - } - - /** - * Private constructor. - * - * Instances have to be created through static {@link SslValidatorDialog#newInstance}. - * - * @param context Android context where the dialog will live - * @param e Exception causing the need of prompt the user about the server certificate. - * @param listener Object to notice when the server certificate was added to the local certificates store. - */ - private SslValidatorDialog(Context context, OnSslValidatorListener listener) { - super(context); - mListener = listener; - } - - - /** - * {@inheritDoc} - */ - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_NO_TITLE); - mView = getLayoutInflater().inflate(R.layout.ssl_validator_layout, null); - setContentView(mView); - - mView.findViewById(R.id.ok).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - try { - saveServerCert(); - dismiss(); - if (mListener != null) - mListener.onSavedCertificate(); - else - Log_OC.d(TAG, "Nobody there to notify the certificate was saved"); - - } catch (GeneralSecurityException e) { - dismiss(); - if (mListener != null) - mListener.onFailedSavingCertificate(); - Log_OC.e(TAG, "Server certificate could not be saved in the known servers trust store ", e); - - } catch (IOException e) { - dismiss(); - if (mListener != null) - mListener.onFailedSavingCertificate(); - Log_OC.e(TAG, "Server certificate could not be saved in the known servers trust store ", e); - } - } - }); - - mView.findViewById(R.id.cancel).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - cancel(); - } - }); - - mView.findViewById(R.id.details_btn).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - View detailsScroll = findViewById(R.id.details_scroll); - if (detailsScroll.getVisibility() == View.VISIBLE) { - detailsScroll.setVisibility(View.GONE); - ((CustomButton)v).setText(R.string.ssl_validator_btn_details_see); - - } else { - detailsScroll.setVisibility(View.VISIBLE); - ((CustomButton)v).setText(R.string.ssl_validator_btn_details_hide); - } - } - }); - } - - - public void updateResult(RemoteOperationResult result) { - if (result.isSslRecoverableException()) { - mException = (CertificateCombinedException) result.getException(); - - /// clean - mView.findViewById(R.id.reason_cert_not_trusted).setVisibility(View.GONE); - mView.findViewById(R.id.reason_cert_expired).setVisibility(View.GONE); - mView.findViewById(R.id.reason_cert_not_yet_valid).setVisibility(View.GONE); - mView.findViewById(R.id.reason_hostname_not_verified).setVisibility(View.GONE); - mView.findViewById(R.id.details_scroll).setVisibility(View.GONE); - - /// refresh - if (mException.getCertPathValidatorException() != null) { - ((TextView)mView.findViewById(R.id.reason_cert_not_trusted)).setVisibility(View.VISIBLE); - } - - if (mException.getCertificateExpiredException() != null) { - ((TextView)mView.findViewById(R.id.reason_cert_expired)).setVisibility(View.VISIBLE); - } - - if (mException.getCertificateNotYetValidException() != null) { - ((TextView)mView.findViewById(R.id.reason_cert_not_yet_valid)).setVisibility(View.VISIBLE); - } - - if (mException.getSslPeerUnverifiedException() != null ) { - ((TextView)mView.findViewById(R.id.reason_hostname_not_verified)).setVisibility(View.VISIBLE); - } - - - showCertificateData(mException.getServerCertificate()); - } - - } - - private void showCertificateData(X509Certificate cert) { - - if (cert != null) { - showSubject(cert.getSubjectX500Principal()); - showIssuer(cert.getIssuerX500Principal()); - showValidity(cert.getNotBefore(), cert.getNotAfter()); - showSignature(cert); - - } else { - // this should not happen - // TODO - } - } - - private void showSignature(X509Certificate cert) { - TextView sigView = ((TextView)mView.findViewById(R.id.value_signature)); - TextView algorithmView = ((TextView)mView.findViewById(R.id.value_signature_algorithm)); - sigView.setText(getHex(cert.getSignature())); - algorithmView.setText(cert.getSigAlgName()); - } - - public String getHex(final byte [] raw) { - if (raw == null) { - return null; - } - final StringBuilder hex = new StringBuilder(2 * raw.length); - for (final byte b : raw) { - final int hiVal = (b & 0xF0) >> 4; - final int loVal = b & 0x0F; - hex.append((char) ('0' + (hiVal + (hiVal / 10 * 7)))); - hex.append((char) ('0' + (loVal + (loVal / 10 * 7)))); - } - return hex.toString(); - } - - private void showValidity(Date notBefore, Date notAfter) { - TextView fromView = ((TextView)mView.findViewById(R.id.value_validity_from)); - TextView toView = ((TextView)mView.findViewById(R.id.value_validity_to)); - fromView.setText(notBefore.toLocaleString()); - toView.setText(notAfter.toLocaleString()); - } - - private void showSubject(X500Principal subject) { - Map s = parsePrincipal(subject); - TextView cnView = ((TextView)mView.findViewById(R.id.value_subject_CN)); - TextView oView = ((TextView)mView.findViewById(R.id.value_subject_O)); - TextView ouView = ((TextView)mView.findViewById(R.id.value_subject_OU)); - TextView cView = ((TextView)mView.findViewById(R.id.value_subject_C)); - TextView stView = ((TextView)mView.findViewById(R.id.value_subject_ST)); - TextView lView = ((TextView)mView.findViewById(R.id.value_subject_L)); - - if (s.get("CN") != null) { - cnView.setText(s.get("CN")); - cnView.setVisibility(View.VISIBLE); - } else { - cnView.setVisibility(View.GONE); - } - if (s.get("O") != null) { - oView.setText(s.get("O")); - oView.setVisibility(View.VISIBLE); - } else { - oView.setVisibility(View.GONE); - } - if (s.get("OU") != null) { - ouView.setText(s.get("OU")); - ouView.setVisibility(View.VISIBLE); - } else { - ouView.setVisibility(View.GONE); - } - if (s.get("C") != null) { - cView.setText(s.get("C")); - cView.setVisibility(View.VISIBLE); - } else { - cView.setVisibility(View.GONE); - } - if (s.get("ST") != null) { - stView.setText(s.get("ST")); - stView.setVisibility(View.VISIBLE); - } else { - stView.setVisibility(View.GONE); - } - if (s.get("L") != null) { - lView.setText(s.get("L")); - lView.setVisibility(View.VISIBLE); - } else { - lView.setVisibility(View.GONE); - } - } - - private void showIssuer(X500Principal issuer) { - Map s = parsePrincipal(issuer); - TextView cnView = ((TextView)mView.findViewById(R.id.value_issuer_CN)); - TextView oView = ((TextView)mView.findViewById(R.id.value_issuer_O)); - TextView ouView = ((TextView)mView.findViewById(R.id.value_issuer_OU)); - TextView cView = ((TextView)mView.findViewById(R.id.value_issuer_C)); - TextView stView = ((TextView)mView.findViewById(R.id.value_issuer_ST)); - TextView lView = ((TextView)mView.findViewById(R.id.value_issuer_L)); - - if (s.get("CN") != null) { - cnView.setText(s.get("CN")); - cnView.setVisibility(View.VISIBLE); - } else { - cnView.setVisibility(View.GONE); - } - if (s.get("O") != null) { - oView.setText(s.get("O")); - oView.setVisibility(View.VISIBLE); - } else { - oView.setVisibility(View.GONE); - } - if (s.get("OU") != null) { - ouView.setText(s.get("OU")); - ouView.setVisibility(View.VISIBLE); - } else { - ouView.setVisibility(View.GONE); - } - if (s.get("C") != null) { - cView.setText(s.get("C")); - cView.setVisibility(View.VISIBLE); - } else { - cView.setVisibility(View.GONE); - } - if (s.get("ST") != null) { - stView.setText(s.get("ST")); - stView.setVisibility(View.VISIBLE); - } else { - stView.setVisibility(View.GONE); - } - if (s.get("L") != null) { - lView.setText(s.get("L")); - lView.setVisibility(View.VISIBLE); - } else { - lView.setVisibility(View.GONE); - } - } - - - private Map parsePrincipal(X500Principal principal) { - Map result = new HashMap(); - String toParse = principal.getName(); - String[] pieces = toParse.split(","); - String[] tokens = {"CN", "O", "OU", "C", "ST", "L"}; - for (int i=0; i < pieces.length ; i++) { - for (int j=0; j= android.os.Build.VERSION_CODES.HONEYCOMB) { - builder.setIconAttribute(android.R.attr.alertDialogIcon); - } - - if (posBtn != -1) - builder.setPositiveButton(posBtn, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - mListener.onConfirmation(getTag()); - dialog.dismiss(); - } - }); - if (neuBtn != -1) - builder.setNeutralButton(neuBtn, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - mListener.onNeutral(getTag()); - dialog.dismiss(); - } - }); - if (negBtn != -1) - builder.setNegativeButton(negBtn, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mListener.onCancel(getTag()); - dialog.dismiss(); - } - }); - return builder.create(); - } - - - public interface ConfirmationDialogFragmentListener { - public void onConfirmation(String callerTag); - public void onNeutral(String callerTag); - public void onCancel(String callerTag); - } - -} - diff --git a/src/de/mobilcom/debitel/cloud/android/ui/fragment/ExtendedListFragment.java b/src/de/mobilcom/debitel/cloud/android/ui/fragment/ExtendedListFragment.java deleted file mode 100644 index b152b255..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/fragment/ExtendedListFragment.java +++ /dev/null @@ -1,119 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.ui.fragment; - -import com.actionbarsherlock.app.SherlockFragment; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.ui.ExtendedListView; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ListAdapter; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ListView; - -/** - * TODO extending SherlockListFragment instead of SherlockFragment - */ -public class ExtendedListFragment extends SherlockFragment implements OnItemClickListener { - - private static final String TAG = ExtendedListFragment.class.getSimpleName(); - - private static final String KEY_SAVED_LIST_POSITION = "SAVED_LIST_POSITION"; - - protected ExtendedListView mList; - - public void setListAdapter(ListAdapter listAdapter) { - mList.setAdapter(listAdapter); - mList.invalidate(); - } - - public ListView getListView() { - return mList; - } - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - Log_OC.e(TAG, "onCreateView"); - //mList = new ExtendedListView(getActivity()); - View v = inflater.inflate(R.layout.list_fragment, null); - mList = (ExtendedListView)(v.findViewById(R.id.list_root)); - mList.setOnItemClickListener(this); - //mList.setEmptyView(v.findViewById(R.id.empty_list_view)); // looks like it's not a cool idea - 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); - } - - return v; - } - - - @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - super.onSaveInstanceState(savedInstanceState); - Log_OC.e(TAG, "onSaveInstanceState()"); - savedInstanceState.putInt(KEY_SAVED_LIST_POSITION, getReferencePosition()); - } - - - /** - * Calculates the position of the item that will be used as a reference to reposition the visible items in the list when - * the device is turned to other position. - * - * THe current policy is take as a reference the visible item in the center of the screen. - * - * @return The position in the list of the visible item in the center of the screen. - */ - protected int getReferencePosition() { - if (mList != null) { - return (mList.getFirstVisiblePosition() + mList.getLastVisiblePosition()) / 2; - } else { - return 0; - } - } - - - /** - * Sets the visible part of the list from the reference position. - * - * @param position Reference position previously returned by {@link LocalFileListFragment#getReferencePosition()} - */ - protected void setReferencePosition(int position) { - if (mList != null) { - mList.setAndCenterSelection(position); - } - } - - @Override - public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) { - // to be @overriden - } - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/fragment/FileDetailFragment.java b/src/de/mobilcom/debitel/cloud/android/ui/fragment/FileDetailFragment.java deleted file mode 100644 index bb0e4b91..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/fragment/FileDetailFragment.java +++ /dev/null @@ -1,930 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.ui.fragment; - -import java.io.File; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; - -import android.accounts.Account; -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.os.Handler; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -import com.actionbarsherlock.view.MenuItem; - -import de.mobilcom.debitel.cloud.android.DisplayUtils; -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.datamodel.FileDataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.files.services.FileObserverService; -import de.mobilcom.debitel.cloud.android.files.services.FileUploader; -import de.mobilcom.debitel.cloud.android.files.services.FileDownloader.FileDownloaderBinder; -import de.mobilcom.debitel.cloud.android.files.services.FileUploader.FileUploaderBinder; -import de.mobilcom.debitel.cloud.android.operations.OnRemoteOperationListener; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult; -import de.mobilcom.debitel.cloud.android.operations.RemoveFileOperation; -import de.mobilcom.debitel.cloud.android.operations.RenameFileOperation; -import de.mobilcom.debitel.cloud.android.operations.SynchronizeFileOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult.ResultCode; -import de.mobilcom.debitel.cloud.android.ui.activity.ConflictsResolveActivity; -import de.mobilcom.debitel.cloud.android.ui.activity.FileActivity; -import de.mobilcom.debitel.cloud.android.ui.activity.FileDisplayActivity; -import de.mobilcom.debitel.cloud.android.ui.dialog.EditNameDialog; -import de.mobilcom.debitel.cloud.android.ui.dialog.EditNameDialog.EditNameDialogListener; -import de.mobilcom.debitel.cloud.android.ui.preview.PreviewImageFragment; - -import eu.alefzero.webdav.OnDatatransferProgressListener; - -/** - * This Fragment is used to display the details about a file. - * - * @author Bartek Przybylski - * @author David A. Velasco - */ -public class FileDetailFragment extends FileFragment implements - OnClickListener, - ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener, EditNameDialogListener { - - private FileFragment.ContainerActivity mContainerActivity; - - private int mLayout; - private View mView; - private Account mAccount; - private FileDataStorageManager mStorageManager; - - private UploadFinishReceiver mUploadFinishReceiver; - public ProgressListener mProgressListener; - - private Handler mHandler; - private RemoteOperation mLastRemoteOperation; - - private static final String TAG = FileDetailFragment.class.getSimpleName(); - public static final String FTAG_CONFIRMATION = "REMOVE_CONFIRMATION_FRAGMENT"; - - - /** - * Creates an empty details fragment. - * - * It's necessary to keep a public constructor without parameters; the system uses it when tries to reinstantiate a fragment automatically. - */ - public FileDetailFragment() { - super(); - mAccount = null; - mStorageManager = null; - mLayout = R.layout.file_details_empty; - mProgressListener = null; - } - - /** - * Creates a details fragment. - * - * When 'fileToDetail' or 'ocAccount' are null, creates a dummy layout (to use when a file wasn't tapped before). - * - * @param fileToDetail An {@link OCFile} to show in the fragment - * @param ocAccount An ownCloud account; needed to start downloads - */ - public FileDetailFragment(OCFile fileToDetail, Account ocAccount) { - super(fileToDetail); - mAccount = ocAccount; - mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment - mLayout = R.layout.file_details_empty; - mProgressListener = null; - } - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mHandler = new Handler(); - setHasOptionsMenu(true); - } - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - //super.onCreateView(inflater, container, savedInstanceState); - - if (savedInstanceState != null) { - setFile((OCFile)savedInstanceState.getParcelable(FileActivity.EXTRA_FILE)); - mAccount = savedInstanceState.getParcelable(FileActivity.EXTRA_ACCOUNT); - } - - if(getFile() != null && mAccount != null) { - mLayout = R.layout.file_details_fragment; - } - - View view = null; - //view = inflater.inflate(mLayout, container, false); - view = inflater.inflate(mLayout, null); - mView = view; - - if (mLayout == R.layout.file_details_fragment) { - mView.findViewById(R.id.fdKeepInSync).setOnClickListener(this); - ProgressBar progressBar = (ProgressBar)mView.findViewById(R.id.fdProgressBar); - mProgressListener = new ProgressListener(progressBar); - mView.findViewById(R.id.fdCancelBtn).setOnClickListener(this); - } - - updateFileDetails(false, false); - return view; - } - - /** - * {@inheritDoc} - */ - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - try { - mContainerActivity = (ContainerActivity) activity; - - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() + " must implement " + FileDetailFragment.ContainerActivity.class.getSimpleName()); - } - } - - - /** - * {@inheritDoc} - */ - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if (mAccount != null) { - mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver()); - } - } - - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putParcelable(FileActivity.EXTRA_FILE, getFile()); - outState.putParcelable(FileActivity.EXTRA_ACCOUNT, mAccount); - } - - @Override - public void onStart() { - super.onStart(); - listenForTransferProgress(); - } - - @Override - public void onResume() { - super.onResume(); - mUploadFinishReceiver = new UploadFinishReceiver(); - FileUploader fileUploader = new FileUploader(); - IntentFilter filter = new IntentFilter(fileUploader.getUploadFinishMessage()); - getActivity().registerReceiver(mUploadFinishReceiver, filter); - - } - - - @Override - public void onPause() { - super.onPause(); - if (mUploadFinishReceiver != null) { - getActivity().unregisterReceiver(mUploadFinishReceiver); - mUploadFinishReceiver = null; - } - } - - - @Override - public void onStop() { - super.onStop(); - leaveTransferProgress(); - } - - - @Override - public View getView() { - return super.getView() == null ? mView : super.getView(); - } - - - /** - * {@inheritDoc} - */ - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.file_actions_menu, menu); - MenuItem item = menu.findItem(R.id.action_see_details); - if (item != null) { - item.setVisible(false); - item.setEnabled(false); - } - } - - - /** - * {@inheritDoc} - */ - @Override - public void onPrepareOptionsMenu (Menu menu) { - super.onPrepareOptionsMenu(menu); - - List toHide = new ArrayList(); - List toShow = new ArrayList(); - OCFile file = getFile(); - - FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); - boolean downloading = downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file); - FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); - boolean uploading = uploaderBinder != null && uploaderBinder.isUploading(mAccount, getFile()); - - if (downloading || uploading) { - toHide.add(R.id.action_download_file); - toHide.add(R.id.action_rename_file); - toHide.add(R.id.action_remove_file); - toHide.add(R.id.action_open_file_with); - if (!downloading) { - toHide.add(R.id.action_cancel_download); - toShow.add(R.id.action_cancel_upload); - } else { - toHide.add(R.id.action_cancel_upload); - toShow.add(R.id.action_cancel_download); - } - - } else if (file != null && file.isDown()) { - toHide.add(R.id.action_download_file); - toHide.add(R.id.action_cancel_download); - toHide.add(R.id.action_cancel_upload); - - toShow.add(R.id.action_rename_file); - toShow.add(R.id.action_remove_file); - toShow.add(R.id.action_open_file_with); - toShow.add(R.id.action_sync_file); - - } else if (file != null) { - toHide.add(R.id.action_open_file_with); - toHide.add(R.id.action_cancel_download); - toHide.add(R.id.action_cancel_upload); - toHide.add(R.id.action_sync_file); - - toShow.add(R.id.action_rename_file); - toShow.add(R.id.action_remove_file); - toShow.add(R.id.action_download_file); - - } else { - toHide.add(R.id.action_open_file_with); - toHide.add(R.id.action_cancel_download); - toHide.add(R.id.action_cancel_upload); - toHide.add(R.id.action_sync_file); - toHide.add(R.id.action_download_file); - toHide.add(R.id.action_rename_file); - toHide.add(R.id.action_remove_file); - - } - - MenuItem item = null; - for (int i : toHide) { - item = menu.findItem(i); - if (item != null) { - item.setVisible(false); - item.setEnabled(false); - } - } - for (int i : toShow) { - item = menu.findItem(i); - if (item != null) { - item.setVisible(true); - item.setEnabled(true); - } - } - } - - - /** - * {@inheritDoc} - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_open_file_with: { - mContainerActivity.openFile(getFile()); - return true; - } - case R.id.action_remove_file: { - removeFile(); - return true; - } - case R.id.action_rename_file: { - renameFile(); - return true; - } - case R.id.action_download_file: - case R.id.action_cancel_download: - case R.id.action_cancel_upload: - case R.id.action_sync_file: { - synchronizeFile(); - return true; - } - default: - return false; - } - } - - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.fdKeepInSync: { - toggleKeepInSync(); - break; - } - case R.id.fdCancelBtn: { - synchronizeFile(); - break; - } - default: - Log_OC.e(TAG, "Incorrect view clicked!"); - } - } - - - private void toggleKeepInSync() { - CheckBox cb = (CheckBox) getView().findViewById(R.id.fdKeepInSync); - OCFile file = getFile(); - file.setKeepInSync(cb.isChecked()); - mStorageManager.saveFile(file); - - /// register the OCFile instance in the observer service to monitor local updates; - /// if necessary, the file is download - Intent intent = new Intent(getActivity().getApplicationContext(), - FileObserverService.class); - intent.putExtra(FileObserverService.KEY_FILE_CMD, - (cb.isChecked()? - FileObserverService.CMD_ADD_OBSERVED_FILE: - FileObserverService.CMD_DEL_OBSERVED_FILE)); - intent.putExtra(FileObserverService.KEY_CMD_ARG_FILE, file); - intent.putExtra(FileObserverService.KEY_CMD_ARG_ACCOUNT, mAccount); - getActivity().startService(intent); - - if (file.keepInSync()) { - synchronizeFile(); // force an immediate synchronization - } - } - - - private void removeFile() { - OCFile file = getFile(); - ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance( - R.string.confirmation_remove_alert, - new String[]{file.getFileName()}, - file.isDown() ? R.string.confirmation_remove_remote_and_local : R.string.confirmation_remove_remote, - file.isDown() ? R.string.confirmation_remove_local : -1, - R.string.common_cancel); - confDialog.setOnConfirmationListener(this); - confDialog.show(getFragmentManager(), FTAG_CONFIRMATION); - } - - - private void renameFile() { - OCFile file = getFile(); - String fileName = file.getFileName(); - int extensionStart = file.isDirectory() ? -1 : fileName.lastIndexOf("."); - int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length(); - EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this); - dialog.show(getFragmentManager(), "nameeditdialog"); - } - - private void synchronizeFile() { - OCFile file = getFile(); - FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); - FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); - if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) { - downloaderBinder.cancel(mAccount, file); - if (file.isDown()) { - setButtonsForDown(); - } else { - setButtonsForRemote(); - } - - } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file)) { - uploaderBinder.cancel(mAccount, file); - if (!file.fileExists()) { - // TODO make something better - ((FileDisplayActivity)getActivity()).cleanSecondFragment(); - - } else if (file.isDown()) { - setButtonsForDown(); - } else { - setButtonsForRemote(); - } - - } else { - mLastRemoteOperation = new SynchronizeFileOperation(file, null, mStorageManager, mAccount, true, false, getActivity()); - mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); - - // update ui - ((FileDisplayActivity) getActivity()).showLoadingDialog(); - - } - } - - @Override - public void onConfirmation(String callerTag) { - OCFile file = getFile(); - if (callerTag.equals(FTAG_CONFIRMATION)) { - if (mStorageManager.getFileById(file.getFileId()) != null) { - mLastRemoteOperation = new RemoveFileOperation( file, - true, - mStorageManager); - mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); - - ((FileDisplayActivity) getActivity()).showLoadingDialog(); - } - } - } - - @Override - public void onNeutral(String callerTag) { - File f = null; - OCFile file = getFile(); - if (file.isDown() && (f = new File(file.getStoragePath())).exists()) { - f.delete(); - file.setStoragePath(null); - mStorageManager.saveFile(file); - updateFileDetails(file, mAccount); - } - } - - @Override - public void onCancel(String callerTag) { - Log_OC.d(TAG, "REMOVAL CANCELED"); - } - - - /** - * Check if the fragment was created with an empty layout. An empty fragment can't show file details, must be replaced. - * - * @return True when the fragment was created with the empty layout. - */ - public boolean isEmpty() { - return (mLayout == R.layout.file_details_empty || getFile() == null || mAccount == null); - } - - - /** - * Use this method to signal this Activity that it shall update its view. - * - * @param file : An {@link OCFile} - */ - public void updateFileDetails(OCFile file, Account ocAccount) { - setFile(file); - if (ocAccount != null && ( - mStorageManager == null || - (mAccount != null && !mAccount.equals(ocAccount)) - )) { - mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver()); - } - mAccount = ocAccount; - updateFileDetails(false, false); - } - - /** - * Updates the view with all relevant details about that file. - * - * TODO Remove parameter when the transferring state of files is kept in database. - * - * TODO REFACTORING! this method called 5 times before every time the fragment is shown! - * - * @param transferring Flag signaling if the file should be considered as downloading or uploading, - * although {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and - * {@link FileUploaderBinder#isUploading(Account, OCFile)} return false. - * - * @param refresh If 'true', try to refresh the hold file from the database - */ - public void updateFileDetails(boolean transferring, boolean refresh) { - - if (readyToShow()) { - - if (refresh && mStorageManager != null) { - setFile(mStorageManager.getFileByPath(getFile().getRemotePath())); - } - OCFile file = getFile(); - - // set file details - setFilename(file.getFileName()); - setFiletype(file.getMimetype()); - setFilesize(file.getFileLength()); - if(ocVersionSupportsTimeCreated()){ - setTimeCreated(file.getCreationTimestamp()); - } - - setTimeModified(file.getModificationTimestamp()); - - CheckBox cb = (CheckBox)getView().findViewById(R.id.fdKeepInSync); - cb.setChecked(file.keepInSync()); - - // configure UI for depending upon local state of the file - //if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath()) || FileUploader.isUploading(mAccount, mFile.getRemotePath())) { - FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); - FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); - if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file))) { - setButtonsForTransferring(); - - } else if (file.isDown()) { - - setButtonsForDown(); - - } else { - // TODO load default preview image; when the local file is removed, the preview remains there - setButtonsForRemote(); - } - } - getView().invalidate(); - } - - /** - * Checks if the fragment is ready to show details of a OCFile - * - * @return 'True' when the fragment is ready to show details of a file - */ - private boolean readyToShow() { - return (getFile() != null && mAccount != null && mLayout == R.layout.file_details_fragment); - } - - - /** - * Updates the filename in view - * @param filename to set - */ - private void setFilename(String filename) { - TextView tv = (TextView) getView().findViewById(R.id.fdFilename); - if (tv != null) - tv.setText(filename); - } - - /** - * Updates the MIME type in view - * @param mimetype to set - */ - private void setFiletype(String mimetype) { - TextView tv = (TextView) getView().findViewById(R.id.fdType); - if (tv != null) { - String printableMimetype = DisplayUtils.convertMIMEtoPrettyPrint(mimetype);; - tv.setText(printableMimetype); - } - ImageView iv = (ImageView) getView().findViewById(R.id.fdIcon); - if (iv != null) { - iv.setImageResource(DisplayUtils.getResourceId(mimetype)); - } - } - - /** - * Updates the file size in view - * @param filesize in bytes to set - */ - private void setFilesize(long filesize) { - TextView tv = (TextView) getView().findViewById(R.id.fdSize); - if (tv != null) - tv.setText(DisplayUtils.bytesToHumanReadable(filesize)); - } - - /** - * Updates the time that the file was created in view - * @param milliseconds Unix time to set - */ - private void setTimeCreated(long milliseconds){ - TextView tv = (TextView) getView().findViewById(R.id.fdCreated); - TextView tvLabel = (TextView) getView().findViewById(R.id.fdCreatedLabel); - if(tv != null){ - tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds)); - tv.setVisibility(View.VISIBLE); - tvLabel.setVisibility(View.VISIBLE); - } - } - - /** - * Updates the time that the file was last modified - * @param milliseconds Unix time to set - */ - private void setTimeModified(long milliseconds){ - TextView tv = (TextView) getView().findViewById(R.id.fdModified); - if(tv != null){ - tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds)); - } - } - - /** - * Enables or disables buttons for a file being downloaded - */ - private void setButtonsForTransferring() { - if (!isEmpty()) { - // let's protect the user from himself ;) - getView().findViewById(R.id.fdKeepInSync).setEnabled(false); - - // show the progress bar for the transfer - getView().findViewById(R.id.fdProgressBlock).setVisibility(View.VISIBLE); - TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText); - progressText.setVisibility(View.VISIBLE); - FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); - FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); - if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile())) { - progressText.setText(R.string.downloader_download_in_progress_ticker); - } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, getFile())) { - progressText.setText(R.string.uploader_upload_in_progress_ticker); - } - } - } - - /** - * Enables or disables buttons for a file locally available - */ - private void setButtonsForDown() { - if (!isEmpty()) { - getView().findViewById(R.id.fdKeepInSync).setEnabled(true); - - // hides the progress bar - getView().findViewById(R.id.fdProgressBlock).setVisibility(View.GONE); - TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText); - progressText.setVisibility(View.GONE); - } - } - - /** - * Enables or disables buttons for a file not locally available - */ - private void setButtonsForRemote() { - if (!isEmpty()) { - getView().findViewById(R.id.fdKeepInSync).setEnabled(true); - - // hides the progress bar - getView().findViewById(R.id.fdProgressBlock).setVisibility(View.GONE); - TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText); - progressText.setVisibility(View.GONE); - } - } - - - /** - * In ownCloud 3.X.X and 4.X.X there is a bug that SabreDAV does not return - * the time that the file was created. There is a chance that this will - * be fixed in future versions. Use this method to check if this version of - * ownCloud has this fix. - * @return True, if ownCloud the ownCloud version is supporting creation time - */ - private boolean ocVersionSupportsTimeCreated(){ - /*if(mAccount != null){ - AccountManager accManager = (AccountManager) getActivity().getSystemService(Context.ACCOUNT_SERVICE); - OwnCloudVersion ocVersion = new OwnCloudVersion(accManager - .getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION)); - if(ocVersion.compareTo(new OwnCloudVersion(0x030000)) < 0) { - return true; - } - }*/ - return false; - } - - - /** - * Once the file upload has finished -> update view - * - * Being notified about the finish of an upload is necessary for the next sequence: - * 1. Upload a big file. - * 2. Force a synchronization; if it finished before the upload, the file in transfer will be included in the local database and in the file list - * of its containing folder; the the server includes it in the PROPFIND requests although it's not fully upload. - * 3. Click the file in the list to see its details. - * 4. Wait for the upload finishes; at this moment, the details view must be refreshed to enable the action buttons. - */ - private class UploadFinishReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME); - - if (!isEmpty() && accountName.equals(mAccount.name)) { - boolean uploadWasFine = intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false); - String uploadRemotePath = intent.getStringExtra(FileUploader.EXTRA_REMOTE_PATH); - boolean renamedInUpload = getFile().getRemotePath().equals(intent.getStringExtra(FileUploader.EXTRA_OLD_REMOTE_PATH)); - if (getFile().getRemotePath().equals(uploadRemotePath) || - renamedInUpload) { - if (uploadWasFine) { - setFile(mStorageManager.getFileByPath(uploadRemotePath)); - } - if (renamedInUpload) { - String newName = (new File(uploadRemotePath)).getName(); - Toast msg = Toast.makeText(getActivity().getApplicationContext(), String.format(getString(R.string.filedetails_renamed_in_upload_msg), newName), Toast.LENGTH_LONG); - msg.show(); - } - getSherlockActivity().removeStickyBroadcast(intent); // not the best place to do this; a small refactorization of BroadcastReceivers should be done - - updateFileDetails(false, false); // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server - - // Force the preview if the file is an image - if (uploadWasFine && PreviewImageFragment.canBePreviewed(getFile())) { - ((FileDisplayActivity) mContainerActivity).startImagePreview(getFile()); - } - } - } - } - } - - - public void onDismiss(EditNameDialog dialog) { - if (dialog.getResult()) { - String newFilename = dialog.getNewFilename(); - Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename); - mLastRemoteOperation = new RenameFileOperation( getFile(), - mAccount, - newFilename, - new FileDataStorageManager(mAccount, getActivity().getContentResolver())); - mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); - ((FileDisplayActivity) getActivity()).showLoadingDialog(); - } - } - - - /** - * {@inheritDoc} - */ - @Override - public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { - if (operation.equals(mLastRemoteOperation)) { - if (operation instanceof RemoveFileOperation) { - onRemoveFileOperationFinish((RemoveFileOperation)operation, result); - - } else if (operation instanceof RenameFileOperation) { - onRenameFileOperationFinish((RenameFileOperation)operation, result); - - } else if (operation instanceof SynchronizeFileOperation) { - onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result); - } - } - } - - - private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) { - ((FileDisplayActivity) getActivity()).dismissLoadingDialog(); - if (result.isSuccess()) { - Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG); - msg.show(); - ((FileDisplayActivity)getActivity()).cleanSecondFragment(); - - } else { - Toast msg = Toast.makeText(getActivity(), R.string.remove_fail_msg, Toast.LENGTH_LONG); - msg.show(); - if (result.isSslRecoverableException()) { - // TODO show the SSL warning dialog - } - } - } - - private void onRenameFileOperationFinish(RenameFileOperation operation, RemoteOperationResult result) { - ((FileDisplayActivity) getActivity()).dismissLoadingDialog(); - - if (result.isSuccess()) { - updateFileDetails(((RenameFileOperation)operation).getFile(), mAccount); - mContainerActivity.onFileStateChanged(); - - } else { - if (result.getCode().equals(ResultCode.INVALID_LOCAL_FILE_NAME)) { - Toast msg = Toast.makeText(getActivity(), R.string.rename_local_fail_msg, Toast.LENGTH_LONG); - msg.show(); - // TODO throw again the new rename dialog - } else { - Toast msg = Toast.makeText(getActivity(), R.string.rename_server_fail_msg, Toast.LENGTH_LONG); - msg.show(); - if (result.isSslRecoverableException()) { - // TODO show the SSL warning dialog - } - } - } - } - - private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) { - ((FileDisplayActivity) getActivity()).dismissLoadingDialog(); - OCFile file = getFile(); - if (!result.isSuccess()) { - if (result.getCode() == ResultCode.SYNC_CONFLICT) { - Intent i = new Intent(getActivity(), ConflictsResolveActivity.class); - i.putExtra(ConflictsResolveActivity.EXTRA_FILE, file); - i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mAccount); - startActivity(i); - - } else { - Toast msg = Toast.makeText(getActivity(), R.string.sync_file_fail_msg, Toast.LENGTH_LONG); - msg.show(); - } - - if (file.isDown()) { - setButtonsForDown(); - - } else { - setButtonsForRemote(); - } - - } else { - if (operation.transferWasRequested()) { - setButtonsForTransferring(); - mContainerActivity.onFileStateChanged(); // this is not working; FileDownloader won't do NOTHING at all until this method finishes, so - // checking the service to see if the file is downloading results in FALSE - } else { - Toast msg = Toast.makeText(getActivity(), R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG); - msg.show(); - if (file.isDown()) { - setButtonsForDown(); - - } else { - setButtonsForRemote(); - } - } - } - } - - - public void listenForTransferProgress() { - if (mProgressListener != null) { - if (mContainerActivity.getFileDownloaderBinder() != null) { - mContainerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, getFile()); - } - if (mContainerActivity.getFileUploaderBinder() != null) { - mContainerActivity.getFileUploaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, getFile()); - } - } - } - - - public void leaveTransferProgress() { - if (mProgressListener != null) { - if (mContainerActivity.getFileDownloaderBinder() != null) { - mContainerActivity.getFileDownloaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, getFile()); - } - if (mContainerActivity.getFileUploaderBinder() != null) { - mContainerActivity.getFileUploaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, getFile()); - } - } - } - - - - /** - * Helper class responsible for updating the progress bar shown for file uploading or downloading - * - * @author David A. Velasco - */ - private class ProgressListener implements OnDatatransferProgressListener { - int mLastPercent = 0; - WeakReference mProgressBar = null; - - ProgressListener(ProgressBar progressBar) { - mProgressBar = new WeakReference(progressBar); - } - - @Override - public void onTransferProgress(long progressRate) { - // old method, nothing here - }; - - @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/de/mobilcom/debitel/cloud/android/ui/fragment/FileFragment.java b/src/de/mobilcom/debitel/cloud/android/ui/fragment/FileFragment.java deleted file mode 100644 index df1a0d5b..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/fragment/FileFragment.java +++ /dev/null @@ -1,102 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.fragment; - -import android.support.v4.app.Fragment; - -import com.actionbarsherlock.app.SherlockFragment; - -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.files.FileHandler; -import de.mobilcom.debitel.cloud.android.ui.activity.TransferServiceGetter; - -/** - * Common methods for {@link Fragment}s containing {@link OCFile}s - * - * @author David A. Velasco - * - */ -public class FileFragment extends SherlockFragment { - - private OCFile mFile; - - - /** - * Creates an empty fragment. - * - * It's necessary to keep a public constructor without parameters; the system uses it when tries to reinstantiate a fragment automatically. - */ - public FileFragment() { - mFile = null; - } - - /** - * Creates an instance for a given {@OCFile}. - * - * @param file - */ - public FileFragment(OCFile file) { - mFile = file; - } - - /** - * Getter for the hold {@link OCFile} - * - * @return The {@link OCFile} hold - */ - public OCFile getFile() { - return mFile; - } - - - protected void setFile(OCFile file) { - mFile = file; - } - - /** - * Interface to implement by any Activity that includes some instance of FileFragment - * - * @author David A. Velasco - */ - public interface ContainerActivity extends TransferServiceGetter, FileHandler { - - /** - * Callback method invoked when the detail fragment wants to notice its container - * activity about a relevant state the file shown by the fragment. - * - * Added to notify to FileDisplayActivity about the need of refresh the files list. - * - * Currently called when: - * - a download is started; - * - a rename is completed; - * - a deletion is completed; - * - the 'inSync' flag is changed; - */ - public void onFileStateChanged(); - - /** - * Request the parent activity to show the details of an {@link OCFile}. - * - * @param file File to show details - */ - public void showDetails(OCFile file); - - - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/fragment/LandingPageFragment.java b/src/de/mobilcom/debitel/cloud/android/ui/fragment/LandingPageFragment.java deleted file mode 100644 index 45667ea2..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/fragment/LandingPageFragment.java +++ /dev/null @@ -1,58 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.ui.fragment; - -import com.actionbarsherlock.app.SherlockFragment; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ListView; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.ui.activity.LandingActivity; -import de.mobilcom.debitel.cloud.android.ui.adapter.LandingScreenAdapter; - -/** - * Used on the Landing page to display what Components of the ownCloud there - * are. Like Files, Music, Contacts, etc. - * - * @author Lennart Rosam - * - */ -public class LandingPageFragment extends SherlockFragment { - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View root = inflater.inflate(R.layout.landing_page_fragment, container); - return root; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - ListView landingScreenItems = (ListView) getView().findViewById( - R.id.homeScreenList); - landingScreenItems.setAdapter(new LandingScreenAdapter(getActivity())); - landingScreenItems - .setOnItemClickListener((LandingActivity) getActivity()); - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/fragment/LocalFileListFragment.java b/src/de/mobilcom/debitel/cloud/android/ui/fragment/LocalFileListFragment.java deleted file mode 100644 index aa1e34a4..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/fragment/LocalFileListFragment.java +++ /dev/null @@ -1,250 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 de.mobilcom.debitel.cloud.android.ui.fragment; - -import java.io.File; - - -import android.app.Activity; -import android.os.Bundle; -import android.os.Environment; -import android.util.SparseBooleanArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ImageView; -import android.widget.ListView; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.ui.adapter.LocalFileListAdapter; - -/** - * A Fragment that lists all files and folders in a given LOCAL path. - * - * @author David A. Velasco - * - */ -public class LocalFileListFragment extends ExtendedListFragment { - private static final String TAG = "LocalFileListFragment"; - - /** Reference to the Activity which this fragment is attached to. For callbacks */ - private LocalFileListFragment.ContainerActivity mContainerActivity; - - /** Directory to show */ - private File mDirectory = null; - - /** Adapter to connect the data from the directory with the View object */ - private LocalFileListAdapter mAdapter = null; - - - /** - * {@inheritDoc} - */ - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - try { - mContainerActivity = (ContainerActivity) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() + " must implement " + LocalFileListFragment.ContainerActivity.class.getSimpleName()); - } - } - - - /** - * {@inheritDoc} - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - Log_OC.i(TAG, "onCreateView() start"); - View v = super.onCreateView(inflater, container, savedInstanceState); - getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - Log_OC.i(TAG, "onCreateView() end"); - return v; - } - - - /** - * {@inheritDoc} - */ - @Override - public void onActivityCreated(Bundle savedInstanceState) { - Log_OC.i(TAG, "onActivityCreated() start"); - - super.onCreate(savedInstanceState); - mAdapter = new LocalFileListAdapter(mContainerActivity.getInitialDirectory(), getActivity()); - setListAdapter(mAdapter); - - Log_OC.i(TAG, "onActivityCreated() stop"); - } - - - /** - * Checks the file clicked over. Browses inside if it is a directory. Notifies the container activity in any case. - */ - @Override - public void onItemClick(AdapterView l, View v, int position, long id) { - File file = (File) mAdapter.getItem(position); - if (file != null) { - /// Click on a directory - if (file.isDirectory()) { - // just local updates - listDirectory(file); - // notify the click to container Activity - mContainerActivity.onDirectoryClick(file); - - } else { /// Click on a file - ImageView checkBoxV = (ImageView) v.findViewById(R.id.custom_checkbox); - if (checkBoxV != null) { - if (getListView().isItemChecked(position)) { - checkBoxV.setImageResource(android.R.drawable.checkbox_on_background); - } else { - checkBoxV.setImageResource(android.R.drawable.checkbox_off_background); - } - } - // notify the change to the container Activity - mContainerActivity.onFileClick(file); - } - - } else { - Log_OC.w(TAG, "Null object in ListAdapter!!"); - } - } - - - /** - * Call this, when the user presses the up button - */ - public void onNavigateUp() { - File parentDir = null; - if(mDirectory != null) { - parentDir = mDirectory.getParentFile(); // can be null - } - listDirectory(parentDir); - } - - - /** - * Use this to query the {@link File} object for the directory - * that is currently being displayed by this fragment - * - * @return File The currently displayed directory - */ - public File getCurrentDirectory(){ - return mDirectory; - } - - - /** - * Calls {@link LocalFileListFragment#listDirectory(File)} with a null parameter - * to refresh the current directory. - */ - public void listDirectory(){ - listDirectory(null); - } - - - /** - * Lists the given directory on the view. When the input parameter is null, - * it will either refresh the last known directory. list the root - * if there never was a directory. - * - * @param directory Directory to be listed - */ - public void listDirectory(File directory) { - - // Check input parameters for null - if(directory == null) { - if(mDirectory != null){ - directory = mDirectory; - } else { - directory = Environment.getExternalStorageDirectory(); // TODO be careful with the state of the storage; could not be available - if (directory == null) return; // no files to show - } - } - - - // if that's not a directory -> List its parent - if(!directory.isDirectory()){ - Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString()); - directory = directory.getParentFile(); - } - - mList.clearChoices(); // by now, only files in the same directory will be kept as selected - mAdapter.swapDirectory(directory); - if (mDirectory == null || !mDirectory.equals(directory)) { - mList.setSelectionFromTop(0, 0); - } - mDirectory = directory; - } - - - /** - * Returns the fule paths to the files checked by the user - * - * @return File paths to the files checked by the user. - */ - public String[] getCheckedFilePaths() { - String [] result = null; - SparseBooleanArray positions = mList.getCheckedItemPositions(); - if (positions.size() > 0) { - Log_OC.d(TAG, "Returning " + positions.size() + " selected files"); - result = new String[positions.size()]; - for (int i=0; i. - * - */ -package de.mobilcom.debitel.cloud.android.ui.fragment; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; -import de.mobilcom.debitel.cloud.android.datamodel.DataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.files.FileHandler; -import de.mobilcom.debitel.cloud.android.files.services.FileDownloader.FileDownloaderBinder; -import de.mobilcom.debitel.cloud.android.files.services.FileUploader.FileUploaderBinder; -import de.mobilcom.debitel.cloud.android.operations.OnRemoteOperationListener; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoveFileOperation; -import de.mobilcom.debitel.cloud.android.operations.RenameFileOperation; -import de.mobilcom.debitel.cloud.android.operations.SynchronizeFileOperation; -import de.mobilcom.debitel.cloud.android.ui.activity.FileDisplayActivity; -import de.mobilcom.debitel.cloud.android.ui.activity.TransferServiceGetter; -import de.mobilcom.debitel.cloud.android.ui.adapter.FileListListAdapter; -import de.mobilcom.debitel.cloud.android.ui.dialog.EditNameDialog; -import de.mobilcom.debitel.cloud.android.ui.dialog.EditNameDialog.EditNameDialogListener; -import de.mobilcom.debitel.cloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener; -import de.mobilcom.debitel.cloud.android.ui.preview.PreviewImageFragment; -import de.mobilcom.debitel.cloud.android.ui.preview.PreviewMediaFragment; - -import android.accounts.Account; -import android.app.Activity; -import android.os.Bundle; -import android.os.Handler; -import android.view.ContextMenu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.AdapterContextMenuInfo; - -/** - * A Fragment that lists all files and folders in a given path. - * - * @author Bartek Przybylski - * - */ -public class OCFileListFragment extends ExtendedListFragment implements EditNameDialogListener, ConfirmationDialogFragmentListener { - - private static final String TAG = OCFileListFragment.class.getSimpleName(); - - private static final String MY_PACKAGE = OCFileListFragment.class.getPackage() != null ? OCFileListFragment.class.getPackage().getName() : "de.mobilcom.debitel.cloud.android.ui.fragment"; - private static final String EXTRA_FILE = MY_PACKAGE + ".extra.FILE"; - - private OCFileListFragment.ContainerActivity mContainerActivity; - - private OCFile mFile = null; - private FileListListAdapter mAdapter; - - private Handler mHandler; - private OCFile mTargetFile; - - /** - * {@inheritDoc} - */ - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - Log_OC.e(TAG, "onAttach"); - try { - mContainerActivity = (ContainerActivity) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() + " must implement " + OCFileListFragment.ContainerActivity.class.getSimpleName()); - } - } - - - /** - * {@inheritDoc} - */ - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - Log_OC.e(TAG, "onActivityCreated() start"); - mAdapter = new FileListListAdapter(getActivity(), mContainerActivity); - if (savedInstanceState != null) { - mFile = savedInstanceState.getParcelable(EXTRA_FILE); - } - setListAdapter(mAdapter); - - registerForContextMenu(getListView()); - getListView().setOnCreateContextMenuListener(this); - - mHandler = new Handler(); - - } - - /** - * Saves the current listed folder. - */ - @Override - public void onSaveInstanceState (Bundle outState) { - super.onSaveInstanceState(outState); - outState.putParcelable(EXTRA_FILE, mFile); - } - - - /** - * Call this, when the user presses the up button - */ - public void onBrowseUp() { - OCFile parentDir = null; - - if(mFile != null){ - DataStorageManager storageManager = mContainerActivity.getStorageManager(); - parentDir = storageManager.getFileById(mFile.getParentId()); - mFile = parentDir; - } - listDirectory(parentDir); - } - - @Override - public void onItemClick(AdapterView l, View v, int position, long id) { - OCFile file = (OCFile) mAdapter.getItem(position); - if (file != null) { - if (file.isDirectory()) { - // update state and view of this fragment - listDirectory(file); - // then, notify parent activity to let it update its state and view, and other fragments - mContainerActivity.onBrowsedDownTo(file); - - } else { /// Click on a file - if (PreviewImageFragment.canBePreviewed(file)) { - // preview image - it handles the download, if needed - mContainerActivity.startImagePreview(file); - - } else if (file.isDown()) { - if (PreviewMediaFragment.canBePreviewed(file)) { - // media preview - mContainerActivity.startMediaPreview(file, 0, true); - } else { - // open with - mContainerActivity.openFile(file); - } - - } else { - // automatic download, preview on finish - mContainerActivity.startDownloadForPreview(file); - } - - } - - } else { - Log_OC.d(TAG, "Null object in ListAdapter!!"); - } - - } - - /** - * {@inheritDoc} - */ - @Override - public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - MenuInflater inflater = getActivity().getMenuInflater(); - inflater.inflate(R.menu.file_actions_menu, menu); - AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; - OCFile targetFile = (OCFile) mAdapter.getItem(info.position); - List toHide = new ArrayList(); - List toDisable = new ArrayList(); - - MenuItem item = null; - if (targetFile.isDirectory()) { - // contextual menu for folders - toHide.add(R.id.action_open_file_with); - toHide.add(R.id.action_download_file); - toHide.add(R.id.action_cancel_download); - toHide.add(R.id.action_cancel_upload); - toHide.add(R.id.action_sync_file); - toHide.add(R.id.action_see_details); - if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) || - mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ) { - toDisable.add(R.id.action_rename_file); - toDisable.add(R.id.action_remove_file); - - } - - } else { - // contextual menu for regular files - - // new design: 'download' and 'open with' won't be available anymore in context menu - toHide.add(R.id.action_download_file); - toHide.add(R.id.action_open_file_with); - - if (targetFile.isDown()) { - toHide.add(R.id.action_cancel_download); - toHide.add(R.id.action_cancel_upload); - - } else { - toHide.add(R.id.action_sync_file); - } - if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) { - toHide.add(R.id.action_cancel_upload); - toDisable.add(R.id.action_rename_file); - toDisable.add(R.id.action_remove_file); - - } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) { - toHide.add(R.id.action_cancel_download); - toDisable.add(R.id.action_rename_file); - toDisable.add(R.id.action_remove_file); - - } else { - toHide.add(R.id.action_cancel_download); - toHide.add(R.id.action_cancel_upload); - } - } - - for (int i : toHide) { - item = menu.findItem(i); - if (item != null) { - item.setVisible(false); - item.setEnabled(false); - } - } - - for (int i : toDisable) { - item = menu.findItem(i); - if (item != null) { - item.setEnabled(false); - } - } - } - - - /** - * {@inhericDoc} - */ - @Override - public boolean onContextItemSelected (MenuItem item) { - AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); - mTargetFile = (OCFile) mAdapter.getItem(info.position); - switch (item.getItemId()) { - case R.id.action_rename_file: { - String fileName = mTargetFile.getFileName(); - int extensionStart = mTargetFile.isDirectory() ? -1 : fileName.lastIndexOf("."); - int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length(); - EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this); - dialog.show(getFragmentManager(), EditNameDialog.TAG); - return true; - } - case R.id.action_remove_file: { - int messageStringId = R.string.confirmation_remove_alert; - int posBtnStringId = R.string.confirmation_remove_remote; - int neuBtnStringId = -1; - if (mTargetFile.isDirectory()) { - messageStringId = R.string.confirmation_remove_folder_alert; - posBtnStringId = R.string.confirmation_remove_remote_and_local; - neuBtnStringId = R.string.confirmation_remove_folder_local; - } else if (mTargetFile.isDown()) { - posBtnStringId = R.string.confirmation_remove_remote_and_local; - neuBtnStringId = R.string.confirmation_remove_local; - } - ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance( - messageStringId, - new String[]{mTargetFile.getFileName()}, - posBtnStringId, - neuBtnStringId, - R.string.common_cancel); - confDialog.setOnConfirmationListener(this); - confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION); - return true; - } - case R.id.action_sync_file: { - Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()); - RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, false, getSherlockActivity()); - operation.execute(account, getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity()); - ((FileDisplayActivity) getSherlockActivity()).showLoadingDialog(); - return true; - } - case R.id.action_cancel_download: { - FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); - Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity()); - if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) { - downloaderBinder.cancel(account, mTargetFile); - listDirectory(); - mContainerActivity.onTransferStateChanged(mTargetFile, false, false); - } - return true; - } - case R.id.action_cancel_upload: { - FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); - Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity()); - if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) { - uploaderBinder.cancel(account, mTargetFile); - listDirectory(); - mContainerActivity.onTransferStateChanged(mTargetFile, false, false); - } - return true; - } - case R.id.action_see_details: { - ((FileFragment.ContainerActivity)getActivity()).showDetails(mTargetFile); - return true; - } - default: - return super.onContextItemSelected(item); - } - } - - - /** - * Use this to query the {@link OCFile} that is currently - * being displayed by this fragment - * @return The currently viewed OCFile - */ - public OCFile getCurrentFile(){ - return mFile; - } - - /** - * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter - */ - public void listDirectory(){ - listDirectory(null); - } - - /** - * Lists the given directory on the view. When the input parameter is null, - * it will either refresh the last known directory. list the root - * if there never was a directory. - * - * @param directory File to be listed - */ - public void listDirectory(OCFile directory) { - DataStorageManager storageManager = mContainerActivity.getStorageManager(); - if (storageManager != null) { - - // Check input parameters for null - if(directory == null){ - if(mFile != null){ - directory = mFile; - } else { - directory = storageManager.getFileByPath("/"); - if (directory == null) return; // no files, wait for sync - } - } - - - // If that's not a directory -> List its parent - if(!directory.isDirectory()){ - Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString()); - directory = storageManager.getFileById(directory.getParentId()); - } - - mAdapter.swapDirectory(directory, storageManager); - if (mFile == null || !mFile.equals(directory)) { - mList.setSelectionFromTop(0, 0); - } - mFile = directory; - - } - } - - - - /** - * Interface to implement by any Activity that includes some instance of FileListFragment - * - * @author David A. Velasco - */ - public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener, FileHandler { - - /** - * Callback method invoked when a the user browsed into a different folder through the list of files - * - * @param file - */ - public void onBrowsedDownTo(OCFile folder); - - public void startDownloadForPreview(OCFile file); - - public void startMediaPreview(OCFile file, int i, boolean b); - - public void startImagePreview(OCFile file); - - /** - * Getter for the current DataStorageManager in the container activity - */ - public DataStorageManager getStorageManager(); - - - /** - * Callback method invoked when a the 'transfer state' of a file changes. - * - * This happens when a download or upload is started or ended for a file. - * - * This method is necessary by now to update the user interface of the double-pane layout in tablets - * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)} - * won't provide the needed response before the method where this is called finishes. - * - * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO) - * - * @param file OCFile which state changed. - * @param downloading Flag signaling if the file is now downloading. - * @param uploading Flag signaling if the file is now uploading. - */ - public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading); - - } - - - @Override - public void onDismiss(EditNameDialog dialog) { - if (dialog.getResult()) { - String newFilename = dialog.getNewFilename(); - Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename); - RemoteOperation operation = new RenameFileOperation(mTargetFile, - AccountUtils.getCurrentOwnCloudAccount(getActivity()), - newFilename, - mContainerActivity.getStorageManager()); - operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity()); - ((FileDisplayActivity) getActivity()).showLoadingDialog(); - } - } - - - @Override - public void onConfirmation(String callerTag) { - if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) { - if (mContainerActivity.getStorageManager().getFileById(mTargetFile.getFileId()) != null) { - RemoteOperation operation = new RemoveFileOperation( mTargetFile, - true, - mContainerActivity.getStorageManager()); - operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity()); - - ((FileDisplayActivity) getActivity()).showLoadingDialog(); - } - } - } - - @Override - public void onNeutral(String callerTag) { - File f = null; - if (mTargetFile.isDirectory()) { - // TODO run in a secondary thread? - mContainerActivity.getStorageManager().removeDirectory(mTargetFile, false, true); - - } else if (mTargetFile.isDown() && (f = new File(mTargetFile.getStoragePath())).exists()) { - f.delete(); - mTargetFile.setStoragePath(null); - mContainerActivity.getStorageManager().saveFile(mTargetFile); - } - listDirectory(); - mContainerActivity.onTransferStateChanged(mTargetFile, false, false); - } - - @Override - public void onCancel(String callerTag) { - Log_OC.d(TAG, "REMOVAL CANCELED"); - } - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/preview/FileDownloadFragment.java b/src/de/mobilcom/debitel/cloud/android/ui/preview/FileDownloadFragment.java deleted file mode 100644 index e2eade6f..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/preview/FileDownloadFragment.java +++ /dev/null @@ -1,382 +0,0 @@ -/* ownCloud Android client application - * - * 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 de.mobilcom.debitel.cloud.android.ui.preview; - -import java.lang.ref.WeakReference; - -import android.accounts.Account; -import android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.FragmentStatePagerAdapter; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.ProgressBar; -import android.widget.TextView; - - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.files.services.FileDownloader.FileDownloaderBinder; -import de.mobilcom.debitel.cloud.android.ui.fragment.FileFragment; - -import eu.alefzero.webdav.OnDatatransferProgressListener; - -/** - * This Fragment is used to monitor the progress of a file downloading. - * - * @author David A. Velasco - */ -public class FileDownloadFragment extends FileFragment implements OnClickListener { - - public static final String EXTRA_FILE = "FILE"; - public static final String EXTRA_ACCOUNT = "ACCOUNT"; - private static final String EXTRA_ERROR = "ERROR"; - - private FileFragment.ContainerActivity mContainerActivity; - - private View mView; - private Account mAccount; - - public ProgressListener mProgressListener; - private boolean mListening; - - private static final String TAG = FileDownloadFragment.class.getSimpleName(); - - private boolean mIgnoreFirstSavedState; - private boolean mError; - - - /** - * Creates an empty details fragment. - * - * It's necessary to keep a public constructor without parameters; the system uses it when tries to reinstantiate a fragment automatically. - */ - public FileDownloadFragment() { - super(); - mAccount = null; - mProgressListener = null; - mListening = false; - mIgnoreFirstSavedState = false; - mError = false; - } - - - /** - * Creates a details fragment. - * - * When 'fileToDetail' or 'ocAccount' are null, creates a dummy layout (to use when a file wasn't tapped before). - * - * @param fileToDetail An {@link OCFile} to show in the fragment - * @param ocAccount An ownCloud account; needed to start downloads - * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStatePagerAdapter}; TODO better solution - */ - public FileDownloadFragment(OCFile fileToDetail, Account ocAccount, boolean ignoreFirstSavedState) { - super(fileToDetail); - mAccount = ocAccount; - mProgressListener = null; - mListening = false; - mIgnoreFirstSavedState = ignoreFirstSavedState; - mError = false; - } - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - super.onCreateView(inflater, container, savedInstanceState); - - if (savedInstanceState != null) { - if (!mIgnoreFirstSavedState) { - setFile((OCFile)savedInstanceState.getParcelable(FileDownloadFragment.EXTRA_FILE)); - mAccount = savedInstanceState.getParcelable(FileDownloadFragment.EXTRA_ACCOUNT); - mError = savedInstanceState.getBoolean(FileDownloadFragment.EXTRA_ERROR); - } else { - mIgnoreFirstSavedState = false; - } - } - - View view = null; - view = inflater.inflate(R.layout.file_download_fragment, container, false); - mView = view; - - ProgressBar progressBar = (ProgressBar)mView.findViewById(R.id.progressBar); - mProgressListener = new ProgressListener(progressBar); - - ((ImageButton)mView.findViewById(R.id.cancelBtn)).setOnClickListener(this); - - if (mError) { - setButtonsForRemote(); - } else { - setButtonsForTransferring(); - } - - return view; - } - - - /** - * {@inheritDoc} - */ - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - try { - mContainerActivity = (ContainerActivity) activity; - - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() + " must implement " + FileFragment.ContainerActivity.class.getSimpleName()); - } - } - - - /** - * {@inheritDoc} - */ - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if (mAccount != null) { - //mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());; - } - } - - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putParcelable(FileDownloadFragment.EXTRA_FILE, getFile()); - outState.putParcelable(FileDownloadFragment.EXTRA_ACCOUNT, mAccount); - outState.putBoolean(FileDownloadFragment.EXTRA_ERROR, mError); - } - - @Override - public void onStart() { - super.onStart(); - listenForTransferProgress(); - } - - @Override - public void onResume() { - super.onResume(); - } - - - @Override - public void onPause() { - super.onPause(); - } - - - @Override - public void onStop() { - super.onStop(); - leaveTransferProgress(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - } - - - @Override - public View getView() { - if (!mListening) { - listenForTransferProgress(); - } - return super.getView() == null ? mView : super.getView(); - } - - - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.cancelBtn: { - FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); - if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile())) { - downloaderBinder.cancel(mAccount, getFile()); - getActivity().finish(); // :) - /* - leaveTransferProgress(); - if (mFile.isDown()) { - setButtonsForDown(); - } else { - setButtonsForRemote(); - } - */ - } - break; - } - default: - Log_OC.e(TAG, "Incorrect view clicked!"); - } - } - - - /** - * Updates the view depending upon the state of the downloading file. - * - * @param transferring When true, the view must be updated assuming that the holded file is - * downloading, no matter what the downloaderBinder says. - */ - public void updateView(boolean transferring) { - // configure UI for depending upon local state of the file - FileDownloaderBinder downloaderBinder = (mContainerActivity == null) ? null : mContainerActivity.getFileDownloaderBinder(); - if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile()))) { - setButtonsForTransferring(); - - } else if (getFile().isDown()) { - - setButtonsForDown(); - - } else { - setButtonsForRemote(); - } - getView().invalidate(); - - } - - - /** - * Enables or disables buttons for a file being downloaded - */ - private void setButtonsForTransferring() { - getView().findViewById(R.id.cancelBtn).setVisibility(View.VISIBLE); - - // show the progress bar for the transfer - getView().findViewById(R.id.progressBar).setVisibility(View.VISIBLE); - TextView progressText = (TextView)getView().findViewById(R.id.progressText); - progressText.setText(R.string.downloader_download_in_progress_ticker); - progressText.setVisibility(View.VISIBLE); - - // hides the error icon - getView().findViewById(R.id.errorText).setVisibility(View.GONE); - getView().findViewById(R.id.error_image).setVisibility(View.GONE); - } - - - /** - * Enables or disables buttons for a file locally available - */ - private void setButtonsForDown() { - getView().findViewById(R.id.cancelBtn).setVisibility(View.GONE); - - // hides the progress bar - getView().findViewById(R.id.progressBar).setVisibility(View.GONE); - - // updates the text message - TextView progressText = (TextView)getView().findViewById(R.id.progressText); - progressText.setText(R.string.common_loading); - progressText.setVisibility(View.VISIBLE); - - // hides the error icon - getView().findViewById(R.id.errorText).setVisibility(View.GONE); - getView().findViewById(R.id.error_image).setVisibility(View.GONE); - } - - - /** - * Enables or disables buttons for a file not locally available - * - * Currently, this is only used when a download was failed - */ - private void setButtonsForRemote() { - getView().findViewById(R.id.cancelBtn).setVisibility(View.GONE); - - // hides the progress bar and message - getView().findViewById(R.id.progressBar).setVisibility(View.GONE); - getView().findViewById(R.id.progressText).setVisibility(View.GONE); - - // shows the error icon and message - getView().findViewById(R.id.errorText).setVisibility(View.VISIBLE); - getView().findViewById(R.id.error_image).setVisibility(View.VISIBLE); - } - - - public void listenForTransferProgress() { - if (mProgressListener != null && !mListening) { - if (mContainerActivity.getFileDownloaderBinder() != null) { - mContainerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, getFile()); - mListening = true; - setButtonsForTransferring(); - } - } - } - - - public void leaveTransferProgress() { - if (mProgressListener != null) { - if (mContainerActivity.getFileDownloaderBinder() != null) { - mContainerActivity.getFileDownloaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, getFile()); - mListening = false; - } - } - } - - - /** - * Helper class responsible for updating the progress bar shown for file uploading or downloading - * - * @author David A. Velasco - */ - private class ProgressListener implements OnDatatransferProgressListener { - int mLastPercent = 0; - WeakReference mProgressBar = null; - - ProgressListener(ProgressBar progressBar) { - mProgressBar = new WeakReference(progressBar); - } - - @Override - public void onTransferProgress(long progressRate) { - // old method, nothing here - }; - - @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; - } - - } - - - public void setError(boolean error) { - mError = error; - }; - - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/preview/PreviewImageActivity.java b/src/de/mobilcom/debitel/cloud/android/ui/preview/PreviewImageActivity.java deleted file mode 100644 index 892716d8..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/preview/PreviewImageActivity.java +++ /dev/null @@ -1,467 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.preview; - -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.os.Bundle; -import android.os.IBinder; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.view.ViewPager; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnTouchListener; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.view.MenuItem; -import com.actionbarsherlock.view.Window; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; -import de.mobilcom.debitel.cloud.android.datamodel.DataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.FileDataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.files.services.FileDownloader; -import de.mobilcom.debitel.cloud.android.files.services.FileUploader; -import de.mobilcom.debitel.cloud.android.files.services.FileDownloader.FileDownloaderBinder; -import de.mobilcom.debitel.cloud.android.files.services.FileUploader.FileUploaderBinder; -import de.mobilcom.debitel.cloud.android.ui.activity.FileActivity; -import de.mobilcom.debitel.cloud.android.ui.activity.FileDisplayActivity; -import de.mobilcom.debitel.cloud.android.ui.dialog.LoadingDialog; -import de.mobilcom.debitel.cloud.android.ui.fragment.FileFragment; - -/** - * Holds a swiping galley where image files contained in an ownCloud directory are shown - * - * @author David A. Velasco - */ -public class PreviewImageActivity extends FileActivity implements FileFragment.ContainerActivity, ViewPager.OnPageChangeListener, OnTouchListener { - - public static final int DIALOG_SHORT_WAIT = 0; - - public static final String TAG = PreviewImageActivity.class.getSimpleName(); - - public static final String KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW"; - private static final String KEY_WAITING_FOR_BINDER = "WAITING_FOR_BINDER"; - - private static final String DIALOG_WAIT_TAG = "DIALOG_WAIT"; - - private DataStorageManager mStorageManager; - - private ViewPager mViewPager; - private PreviewImagePagerAdapter mPreviewImagePagerAdapter; - - private FileDownloaderBinder mDownloaderBinder = null; - private ServiceConnection mDownloadConnection, mUploadConnection = null; - private FileUploaderBinder mUploaderBinder = null; - - private boolean mRequestWaitingForBinder; - - private DownloadFinishReceiver mDownloadFinishReceiver; - - private boolean mFullScreen; - - private String mDownloadAddedMessage; - private String mDownloadFinishMessage; - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); - setContentView(R.layout.preview_image_activity); - - ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.hide(); - - mFullScreen = true; - if (savedInstanceState != null) { - mRequestWaitingForBinder = savedInstanceState.getBoolean(KEY_WAITING_FOR_BINDER); - } else { - mRequestWaitingForBinder = false; - } - - FileDownloader downloader = new FileDownloader(); - mDownloadAddedMessage = downloader.getDownloadAddedMessage(); - mDownloadFinishMessage= downloader.getDownloadFinishMessage(); - } - - private void initViewPager() { - // get parent from path - String parentPath = getFile().getRemotePath().substring(0, getFile().getRemotePath().lastIndexOf(getFile().getFileName())); - OCFile parentFolder = mStorageManager.getFileByPath(parentPath); - //OCFile parentFolder = mStorageManager.getFileById(getFile().getParentId()); - if (parentFolder == null) { - // should not be necessary - parentFolder = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR); - } - mPreviewImagePagerAdapter = new PreviewImagePagerAdapter(getSupportFragmentManager(), parentFolder, getAccount(), mStorageManager); - mViewPager = (ViewPager) findViewById(R.id.fragmentPager); - int position = mPreviewImagePagerAdapter.getFilePosition(getFile()); - position = (position >= 0) ? position : 0; - mViewPager.setAdapter(mPreviewImagePagerAdapter); - mViewPager.setOnPageChangeListener(this); - mViewPager.setCurrentItem(position); - if (position == 0 && !getFile().isDown()) { - // this is necessary because mViewPager.setCurrentItem(0) just after setting the adapter does not result in a call to #onPageSelected(0) - mRequestWaitingForBinder = true; - } - } - - - @Override - public void onStart() { - super.onStart(); - mDownloadConnection = new PreviewImageServiceConnection(); - bindService(new Intent(this, FileDownloader.class), mDownloadConnection, Context.BIND_AUTO_CREATE); - mUploadConnection = new PreviewImageServiceConnection(); - bindService(new Intent(this, FileUploader.class), mUploadConnection, Context.BIND_AUTO_CREATE); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(KEY_WAITING_FOR_BINDER, mRequestWaitingForBinder); - } - - - /** Defines callbacks for service binding, passed to bindService() */ - private class PreviewImageServiceConnection implements ServiceConnection { - - @Override - public void onServiceConnected(ComponentName component, IBinder service) { - - if (component.equals(new ComponentName(PreviewImageActivity.this, FileDownloader.class))) { - mDownloaderBinder = (FileDownloaderBinder) service; - if (mRequestWaitingForBinder) { - mRequestWaitingForBinder = false; - Log_OC.d(TAG, "Simulating reselection of current page after connection of download binder"); - onPageSelected(mViewPager.getCurrentItem()); - } - - } else if (component.equals(new ComponentName(PreviewImageActivity.this, FileUploader.class))) { - Log_OC.d(TAG, "Upload service connected"); - mUploaderBinder = (FileUploaderBinder) service; - } else { - return; - } - - } - - @Override - public void onServiceDisconnected(ComponentName component) { - if (component.equals(new ComponentName(PreviewImageActivity.this, FileDownloader.class))) { - Log_OC.d(TAG, "Download service suddenly disconnected"); - mDownloaderBinder = null; - } else if (component.equals(new ComponentName(PreviewImageActivity.this, FileUploader.class))) { - Log_OC.d(TAG, "Upload service suddenly disconnected"); - mUploaderBinder = null; - } - } - }; - - - @Override - public void onStop() { - super.onStop(); - if (mDownloadConnection != null) { - unbindService(mDownloadConnection); - mDownloadConnection = null; - } - if (mUploadConnection != null) { - unbindService(mUploadConnection); - mUploadConnection = null; - } - } - - - @Override - public void onDestroy() { - super.onDestroy(); - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - boolean returnValue = false; - - switch(item.getItemId()){ - case android.R.id.home: - backToDisplayActivity(); - returnValue = true; - break; - default: - returnValue = super.onOptionsItemSelected(item); - } - - return returnValue; - } - - - @Override - protected void onResume() { - super.onResume(); - //Log.e(TAG, "ACTIVITY, ONRESUME"); - mDownloadFinishReceiver = new DownloadFinishReceiver(); - - IntentFilter filter = new IntentFilter(mDownloadFinishMessage); - filter.addAction(mDownloadAddedMessage); - registerReceiver(mDownloadFinishReceiver, filter); - } - - @Override - protected void onPostResume() { - //Log.e(TAG, "ACTIVITY, ONPOSTRESUME"); - super.onPostResume(); - } - - @Override - public void onPause() { - super.onPause(); - unregisterReceiver(mDownloadFinishReceiver); - mDownloadFinishReceiver = null; - } - - - private void backToDisplayActivity() { - finish(); - } - - /** - * Show loading dialog - */ - public void showLoadingDialog() { - // Construct dialog - LoadingDialog loading = new LoadingDialog(getResources().getString(R.string.wait_a_moment)); - FragmentManager fm = getSupportFragmentManager(); - FragmentTransaction ft = fm.beginTransaction(); - loading.show(ft, DIALOG_WAIT_TAG); - - } - - /** - * Dismiss loading dialog - */ - public void dismissLoadingDialog(){ - Fragment frag = getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG); - if (frag != null) { - LoadingDialog loading = (LoadingDialog) frag; - loading.dismiss(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void onFileStateChanged() { - // nothing to do here! - } - - - /** - * {@inheritDoc} - */ - @Override - public FileDownloaderBinder getFileDownloaderBinder() { - return mDownloaderBinder; - } - - - @Override - public FileUploaderBinder getFileUploaderBinder() { - return mUploaderBinder; - } - - - @Override - 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_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this)); - startActivity(showDetailsIntent); - int pos = mPreviewImagePagerAdapter.getFilePosition(file); - file = mPreviewImagePagerAdapter.getFileAt(pos); - - } - - - private void requestForDownload(OCFile file) { - if (mDownloaderBinder == null) { - Log_OC.d(TAG, "requestForDownload called without binder to download service"); - - } 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); - startService(i); - } - } - - /** - * This method will be invoked when a new page becomes selected. Animation is not necessarily complete. - * - * @param Position Position index of the new selected page - */ - @Override - public void onPageSelected(int position) { - if (mDownloaderBinder == null) { - mRequestWaitingForBinder = true; - - } else { - OCFile currentFile = mPreviewImagePagerAdapter.getFileAt(position); - getSupportActionBar().setTitle(currentFile.getFileName()); - if (!currentFile.isDown()) { - if (!mPreviewImagePagerAdapter.pendingErrorAt(position)) { - requestForDownload(currentFile); - } - } - } - } - - /** - * Called when the scroll state changes. Useful for discovering when the user begins dragging, - * when the pager is automatically settling to the current page. when it is fully stopped/idle. - * - * @param State The new scroll state (SCROLL_STATE_IDLE, _DRAGGING, _SETTLING - */ - @Override - public void onPageScrollStateChanged(int state) { - } - - /** - * This method will be invoked when the current page is scrolled, either as part of a programmatically - * initiated smooth scroll or a user initiated touch scroll. - * - * @param position Position index of the first page currently being displayed. - * Page position+1 will be visible if positionOffset is nonzero. - * - * @param positionOffset Value from [0, 1) indicating the offset from the page at position. - * @param positionOffsetPixels Value in pixels indicating the offset from position. - */ - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - } - - - /** - * Class waiting for broadcast events from the {@link FielDownloader} service. - * - * Updates the UI when a download is started or finished, provided that it is relevant for the - * folder displayed in the gallery. - */ - private class DownloadFinishReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME); - String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); - if (getAccount().name.equals(accountName) && - downloadedRemotePath != null) { - - OCFile file = mStorageManager.getFileByPath(downloadedRemotePath); - int position = mPreviewImagePagerAdapter.getFilePosition(file); - boolean downloadWasFine = intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false); - //boolean isOffscreen = Math.abs((mViewPager.getCurrentItem() - position)) <= mViewPager.getOffscreenPageLimit(); - - if (position >= 0 && intent.getAction().equals(mDownloadFinishMessage)) { - if (downloadWasFine) { - mPreviewImagePagerAdapter.updateFile(position, file); - - } else { - mPreviewImagePagerAdapter.updateWithDownloadError(position); - } - mPreviewImagePagerAdapter.notifyDataSetChanged(); // will trigger the creation of new fragments - - } else { - Log_OC.d(TAG, "Download finished, but the fragment is offscreen"); - } - - } - removeStickyBroadcast(intent); - } - - } - - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_UP) { - toggleFullScreen(); - } - return true; - } - - - private void toggleFullScreen() { - ActionBar actionBar = getSupportActionBar(); - if (mFullScreen) { - actionBar.show(); - - } else { - actionBar.hide(); - - } - mFullScreen = !mFullScreen; - } - - @Override - protected void onAccountSet(boolean stateWasRecovered) { - if (getAccount() != null) { - OCFile file = getFile(); - /// Validate handled file (first image to preview) - if (file == null) { - throw new IllegalStateException("Instanced with a NULL OCFile"); - } - if (!file.isImage()) { - throw new IllegalArgumentException("Non-image file passed as argument"); - } - mStorageManager = new FileDataStorageManager(getAccount(), getContentResolver()); - - // Update file according to DB file, if it is possible - if (file.getFileId() > DataStorageManager.ROOT_PARENT_ID) - file = mStorageManager.getFileById(file.getFileId()); - - if (file != null) { - /// Refresh the activity according to the Account and OCFile set - setFile(file); // reset after getting it fresh from mStorageManager - getSupportActionBar().setTitle(getFile().getFileName()); - //if (!stateWasRecovered) { - initViewPager(); - //} - - } else { - // handled file not in the current Account - finish(); - } - - } else { - Log_OC.wtf(TAG, "onAccountChanged was called with NULL account associated!"); - } - } - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/preview/PreviewImageFragment.java b/src/de/mobilcom/debitel/cloud/android/ui/preview/PreviewImageFragment.java deleted file mode 100644 index 011803e6..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/preview/PreviewImageFragment.java +++ /dev/null @@ -1,631 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.preview; - -import java.io.File; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; - - -import android.accounts.Account; -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.BitmapFactory.Options; -import android.graphics.Point; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.support.v4.app.FragmentStatePagerAdapter; -import android.view.Display; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnTouchListener; -import android.view.ViewGroup; -import android.webkit.MimeTypeMap; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -import com.actionbarsherlock.view.MenuItem; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.datamodel.FileDataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.operations.OnRemoteOperationListener; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult; -import de.mobilcom.debitel.cloud.android.operations.RemoveFileOperation; -import de.mobilcom.debitel.cloud.android.ui.fragment.ConfirmationDialogFragment; -import de.mobilcom.debitel.cloud.android.ui.fragment.FileFragment; -import eu.alefzero.webdav.WebdavUtils; - - -/** - * This fragment shows a preview of a downloaded image. - * - * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link Account} values will produce an {@link IllegalStateException}. - * - * If the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on instantiation too. - * - * @author David A. Velasco - */ -public class PreviewImageFragment extends FileFragment implements OnRemoteOperationListener, - ConfirmationDialogFragment.ConfirmationDialogFragmentListener { - public static final String EXTRA_FILE = "FILE"; - public static final String EXTRA_ACCOUNT = "ACCOUNT"; - - private View mView; - private Account mAccount; - private FileDataStorageManager mStorageManager; - private ImageView mImageView; - private TextView mMessageView; - private ProgressBar mProgressWheel; - - public Bitmap mBitmap = null; - - private Handler mHandler; - private RemoteOperation mLastRemoteOperation; - - private static final String TAG = PreviewImageFragment.class.getSimpleName(); - - private boolean mIgnoreFirstSavedState; - - - /** - * Creates a fragment to preview an image. - * - * When 'imageFile' or 'ocAccount' are null - * - * @param imageFile An {@link OCFile} to preview as an image in the fragment - * @param ocAccount An ownCloud account; needed to start downloads - * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStatePagerAdapter}; TODO better solution - */ - public PreviewImageFragment(OCFile fileToDetail, Account ocAccount, boolean ignoreFirstSavedState) { - super(fileToDetail); - mAccount = ocAccount; - mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment - mIgnoreFirstSavedState = ignoreFirstSavedState; - } - - - /** - * Creates an empty fragment for image previews. - * - * MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically (for instance, when the device is turned a aside). - * - * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction - */ - public PreviewImageFragment() { - super(); - mAccount = null; - mStorageManager = null; - mIgnoreFirstSavedState = false; - } - - - /** - * {@inheritDoc} - */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mHandler = new Handler(); - setHasOptionsMenu(true); - } - - - /** - * {@inheritDoc} - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - super.onCreateView(inflater, container, savedInstanceState); - mView = inflater.inflate(R.layout.preview_image_fragment, container, false); - mImageView = (ImageView)mView.findViewById(R.id.image); - mImageView.setVisibility(View.GONE); - mView.setOnTouchListener((OnTouchListener)getActivity()); // WATCH OUT THAT CAST - mMessageView = (TextView)mView.findViewById(R.id.message); - mMessageView.setVisibility(View.GONE); - mProgressWheel = (ProgressBar)mView.findViewById(R.id.progressWheel); - mProgressWheel.setVisibility(View.VISIBLE); - return mView; - } - - - /** - * {@inheritDoc} - */ - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - if (!(activity instanceof FileFragment.ContainerActivity)) - throw new ClassCastException(activity.toString() + " must implement " + FileFragment.ContainerActivity.class.getSimpleName()); - } - - - /** - * {@inheritDoc} - */ - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver()); - if (savedInstanceState != null) { - if (!mIgnoreFirstSavedState) { - setFile((OCFile)savedInstanceState.getParcelable(PreviewImageFragment.EXTRA_FILE)); - mAccount = savedInstanceState.getParcelable(PreviewImageFragment.EXTRA_ACCOUNT); - } else { - mIgnoreFirstSavedState = false; - } - } - if (getFile() == null) { - throw new IllegalStateException("Instanced with a NULL OCFile"); - } - if (mAccount == null) { - throw new IllegalStateException("Instanced with a NULL ownCloud Account"); - } - if (!getFile().isDown()) { - throw new IllegalStateException("There is no local file to preview"); - } - } - - - /** - * {@inheritDoc} - */ - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putParcelable(PreviewImageFragment.EXTRA_FILE, getFile()); - outState.putParcelable(PreviewImageFragment.EXTRA_ACCOUNT, mAccount); - } - - - @Override - public void onStart() { - super.onStart(); - if (getFile() != null) { - BitmapLoader bl = new BitmapLoader(mImageView, mMessageView, mProgressWheel); - bl.execute(new String[]{getFile().getStoragePath()}); - } - } - - - /** - * {@inheritDoc} - */ - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - - inflater.inflate(R.menu.file_actions_menu, menu); - List toHide = new ArrayList(); - - MenuItem item = null; - toHide.add(R.id.action_cancel_download); - toHide.add(R.id.action_cancel_upload); - toHide.add(R.id.action_download_file); - toHide.add(R.id.action_rename_file); // by now - - for (int i : toHide) { - item = menu.findItem(i); - if (item != null) { - item.setVisible(false); - item.setEnabled(false); - } - } - - } - - - /** - * {@inheritDoc} - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_open_file_with: { - openFile(); - return true; - } - case R.id.action_remove_file: { - removeFile(); - return true; - } - case R.id.action_see_details: { - seeDetails(); - return true; - } - - default: - return false; - } - } - - - private void seeDetails() { - ((FileFragment.ContainerActivity)getActivity()).showDetails(getFile()); - } - - - @Override - public void onResume() { - super.onResume(); - } - - - @Override - public void onPause() { - super.onPause(); - } - - - @Override - public void onDestroy() { - super.onDestroy(); - if (mBitmap != null) { - mBitmap.recycle(); - } - } - - - /** - * Opens the previewed image with an external application. - * - * TODO - improve this; instead of prioritize the actions available for the MIME type in the server, - * we should get a list of available apps for MIME tpye in the server and join it with the list of - * available apps for the MIME type known from the file extension, to let the user choose - */ - private void openFile() { - OCFile file = getFile(); - String storagePath = file.getStoragePath(); - String encodedStoragePath = WebdavUtils.encodePath(storagePath); - try { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), file.getMimetype()); - i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - startActivity(i); - - } catch (Throwable t) { - Log_OC.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + file.getMimetype()); - boolean toastIt = true; - String mimeType = ""; - try { - Intent i = new Intent(Intent.ACTION_VIEW); - mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1)); - if (mimeType == null || !mimeType.equals(file.getMimetype())) { - if (mimeType != null) { - i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType); - } else { - // desperate try - i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*-/*"); - } - i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - startActivity(i); - toastIt = false; - } - - } catch (IndexOutOfBoundsException e) { - Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath); - - } catch (ActivityNotFoundException e) { - Log_OC.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension"); - - } catch (Throwable th) { - Log_OC.e(TAG, "Unexpected problem when opening: " + storagePath, th); - - } finally { - if (toastIt) { - Toast.makeText(getActivity(), "There is no application to handle file " + file.getFileName(), Toast.LENGTH_SHORT).show(); - } - } - - } - finish(); - } - - - /** - * Starts a the removal of the previewed file. - * - * Shows a confirmation dialog. The action continues in {@link #onConfirmation(String)} , {@link #onNeutral(String)} or {@link #onCancel(String)}, - * depending upon the user selection in the dialog. - */ - private void removeFile() { - ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance( - R.string.confirmation_remove_alert, - new String[]{getFile().getFileName()}, - R.string.confirmation_remove_remote_and_local, - R.string.confirmation_remove_local, - R.string.common_cancel); - confDialog.setOnConfirmationListener(this); - confDialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION); - } - - - /** - * Performs the removal of the previewed file, both locally and in the server. - */ - @Override - public void onConfirmation(String callerTag) { - if (mStorageManager.getFileById(getFile().getFileId()) != null) { // check that the file is still there; - mLastRemoteOperation = new RemoveFileOperation( getFile(), // TODO we need to review the interface with RemoteOperations, and use OCFile IDs instead of OCFile objects as parameters - true, - mStorageManager); - mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); - - ((PreviewImageActivity) getActivity()).showLoadingDialog(); - } - } - - - /** - * Removes the file from local storage - */ - @Override - public void onNeutral(String callerTag) { - // TODO this code should be made in a secondary thread, - OCFile file = getFile(); - if (file.isDown()) { // checks it is still there - File f = new File(file.getStoragePath()); - f.delete(); - file.setStoragePath(null); - mStorageManager.saveFile(file); - finish(); - } - } - - /** - * User cancelled the removal action. - */ - @Override - public void onCancel(String callerTag) { - // nothing to do here - } - - - private class BitmapLoader extends AsyncTask { - - /** - * Weak reference to the target {@link ImageView} where the bitmap will be loaded into. - * - * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes. - */ - private final WeakReference mImageViewRef; - - /** - * Weak reference to the target {@link TextView} where error messages will be written. - * - * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes. - */ - private final WeakReference mMessageViewRef; - - - /** - * Weak reference to the target {@link Progressbar} shown while the load is in progress. - * - * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes. - */ - private final WeakReference mProgressWheelRef; - - - /** - * Error message to show when a load fails - */ - private int mErrorMessageId; - - - /** - * Constructor. - * - * @param imageView Target {@link ImageView} where the bitmap will be loaded into. - */ - public BitmapLoader(ImageView imageView, TextView messageView, ProgressBar progressWheel) { - mImageViewRef = new WeakReference(imageView); - mMessageViewRef = new WeakReference(messageView); - mProgressWheelRef = new WeakReference(progressWheel); - } - - - @SuppressWarnings("deprecation") - @SuppressLint({ "NewApi", "NewApi", "NewApi" }) // to avoid Lint errors since Android SDK r20 - @Override - protected Bitmap doInBackground(String... params) { - Bitmap result = null; - if (params.length != 1) return result; - String storagePath = params[0]; - try { - // set desired options that will affect the size of the bitmap - BitmapFactory.Options options = new Options(); - options.inScaled = true; - options.inPurgeable = true; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - options.inPreferQualityOverSpeed = false; - } - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { - options.inMutable = false; - } - // make a false load of the bitmap - just to be able to read outWidth, outHeight and outMimeType - options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(storagePath, options); - - int width = options.outWidth; - int height = options.outHeight; - int scale = 1; - - Display display = getActivity().getWindowManager().getDefaultDisplay(); - Point size = new Point(); - int screenWidth; - int screenHeight; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR2) { - display.getSize(size); - screenWidth = size.x; - screenHeight = size.y; - } else { - screenWidth = display.getWidth(); - screenHeight = display.getHeight(); - } - - if (width > screenWidth) { - // second try to scale down the image , this time depending upon the screen size - scale = (int) Math.floor((float)width / screenWidth); - } - if (height > screenHeight) { - scale = Math.max(scale, (int) Math.floor((float)height / screenHeight)); - } - options.inSampleSize = scale; - - // really load the bitmap - options.inJustDecodeBounds = false; // the next decodeFile call will be real - result = BitmapFactory.decodeFile(storagePath, options); - //Log_OC.d(TAG, "Image loaded - width: " + options.outWidth + ", loaded height: " + options.outHeight); - - if (result == null) { - mErrorMessageId = R.string.preview_image_error_unknown_format; - Log_OC.e(TAG, "File could not be loaded as a bitmap: " + storagePath); - } - - } catch (OutOfMemoryError e) { - mErrorMessageId = R.string.preview_image_error_unknown_format; - Log_OC.e(TAG, "Out of memory occured for file " + storagePath, e); - - } catch (NoSuchFieldError e) { - mErrorMessageId = R.string.common_error_unknown; - Log_OC.e(TAG, "Error from access to unexisting field despite protection; file " + storagePath, e); - - } catch (Throwable t) { - mErrorMessageId = R.string.common_error_unknown; - Log_OC.e(TAG, "Unexpected error loading " + getFile().getStoragePath(), t); - - } - return result; - } - - @Override - protected void onPostExecute(Bitmap result) { - hideProgressWheel(); - if (result != null) { - showLoadedImage(result); - } else { - showErrorMessage(); - } - } - - private void showLoadedImage(Bitmap result) { - if (mImageViewRef != null) { - final ImageView imageView = mImageViewRef.get(); - if (imageView != null) { - imageView.setImageBitmap(result); - imageView.setVisibility(View.VISIBLE); - mBitmap = result; - } // else , silently finish, the fragment was destroyed - } - if (mMessageViewRef != null) { - final TextView messageView = mMessageViewRef.get(); - if (messageView != null) { - messageView.setVisibility(View.GONE); - } // else , silently finish, the fragment was destroyed - } - } - - private void showErrorMessage() { - if (mImageViewRef != null) { - final ImageView imageView = mImageViewRef.get(); - if (imageView != null) { - // shows the default error icon - imageView.setVisibility(View.VISIBLE); - } // else , silently finish, the fragment was destroyed - } - if (mMessageViewRef != null) { - final TextView messageView = mMessageViewRef.get(); - if (messageView != null) { - messageView.setText(mErrorMessageId); - messageView.setVisibility(View.VISIBLE); - } // else , silently finish, the fragment was destroyed - } - } - - private void hideProgressWheel() { - if (mProgressWheelRef != null) { - final ProgressBar progressWheel = mProgressWheelRef.get(); - if (progressWheel != null) { - progressWheel.setVisibility(View.GONE); - } - } - } - - } - - /** - * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewImageFragment} to be previewed. - * - * @param file File to test if can be previewed. - * @return 'True' if the file can be handled by the fragment. - */ - public static boolean canBePreviewed(OCFile file) { - return (file != null && file.isImage()); - } - - - /** - * {@inheritDoc} - */ - @Override - public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { - if (operation.equals(mLastRemoteOperation) && operation instanceof RemoveFileOperation) { - onRemoveFileOperationFinish((RemoveFileOperation)operation, result); - } - } - - private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) { - ((PreviewImageActivity) getActivity()).dismissLoadingDialog(); - - if (result.isSuccess()) { - Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG); - msg.show(); - finish(); - - } else { - Toast msg = Toast.makeText(getActivity(), R.string.remove_fail_msg, Toast.LENGTH_LONG); - msg.show(); - if (result.isSslRecoverableException()) { - // TODO show the SSL warning dialog - } - } - } - - /** - * Finishes the preview - */ - private void finish() { - Activity container = getActivity(); - container.finish(); - } - - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/preview/PreviewImagePagerAdapter.java b/src/de/mobilcom/debitel/cloud/android/ui/preview/PreviewImagePagerAdapter.java deleted file mode 100644 index 8fcf61b0..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/preview/PreviewImagePagerAdapter.java +++ /dev/null @@ -1,332 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.preview; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.Vector; - -import android.accounts.Account; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentStatePagerAdapter; -import android.view.ViewGroup; - -import de.mobilcom.debitel.cloud.android.datamodel.DataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.ui.fragment.FileFragment; - -/** - * Adapter class that provides Fragment instances - * - * @author David A. Velasco - */ -//public class PreviewImagePagerAdapter extends PagerAdapter { -public class PreviewImagePagerAdapter extends FragmentStatePagerAdapter { - - private Vector mImageFiles; - private Account mAccount; - private Set mObsoleteFragments; - private Set mObsoletePositions; - private Set mDownloadErrors; - private DataStorageManager mStorageManager; - - private Map mCachedFragments; - - /** - * Constructor. - * - * @param fragmentManager {@link FragmentManager} instance that will handle the {@link Fragment}s provided by the adapter. - * @param parentFolder Folder where images will be searched for. - * @param storageManager Bridge to database. - */ - public PreviewImagePagerAdapter(FragmentManager fragmentManager, OCFile parentFolder, Account account, DataStorageManager storageManager) { - super(fragmentManager); - - if (fragmentManager == null) { - throw new IllegalArgumentException("NULL FragmentManager instance"); - } - if (parentFolder == null) { - throw new IllegalArgumentException("NULL parent folder"); - } - if (storageManager == null) { - throw new IllegalArgumentException("NULL storage manager"); - } - - mAccount = account; - mStorageManager = storageManager; - mImageFiles = mStorageManager.getDirectoryImages(parentFolder); - mObsoleteFragments = new HashSet(); - mObsoletePositions = new HashSet(); - mDownloadErrors = new HashSet(); - //mFragmentManager = fragmentManager; - mCachedFragments = new HashMap(); - } - - - /** - * Returns the image files handled by the adapter. - * - * @return A vector with the image files handled by the adapter. - */ - protected OCFile getFileAt(int position) { - return mImageFiles.get(position); - } - - - public Fragment getItem(int i) { - OCFile file = mImageFiles.get(i); - Fragment fragment = null; - if (file.isDown()) { - fragment = new PreviewImageFragment(file, mAccount, mObsoletePositions.contains(Integer.valueOf(i))); - - } else if (mDownloadErrors.contains(Integer.valueOf(i))) { - fragment = new FileDownloadFragment(file, mAccount, true); - ((FileDownloadFragment)fragment).setError(true); - mDownloadErrors.remove(Integer.valueOf(i)); - - } else { - fragment = new FileDownloadFragment(file, mAccount, mObsoletePositions.contains(Integer.valueOf(i))); - } - mObsoletePositions.remove(Integer.valueOf(i)); - return fragment; - } - - public int getFilePosition(OCFile file) { - return mImageFiles.indexOf(file); - } - - @Override - public int getCount() { - return mImageFiles.size(); - } - - @Override - public CharSequence getPageTitle(int position) { - return mImageFiles.get(position).getFileName(); - } - - - public void updateFile(int position, OCFile file) { - FileFragment fragmentToUpdate = mCachedFragments.get(Integer.valueOf(position)); - if (fragmentToUpdate != null) { - mObsoleteFragments.add(fragmentToUpdate); - } - mObsoletePositions.add(Integer.valueOf(position)); - mImageFiles.set(position, file); - } - - - public void updateWithDownloadError(int position) { - FileFragment fragmentToUpdate = mCachedFragments.get(Integer.valueOf(position)); - if (fragmentToUpdate != null) { - mObsoleteFragments.add(fragmentToUpdate); - } - mDownloadErrors.add(Integer.valueOf(position)); - } - - public void clearErrorAt(int position) { - FileFragment fragmentToUpdate = mCachedFragments.get(Integer.valueOf(position)); - if (fragmentToUpdate != null) { - mObsoleteFragments.add(fragmentToUpdate); - } - mDownloadErrors.remove(Integer.valueOf(position)); - } - - - @Override - public int getItemPosition(Object object) { - if (mObsoleteFragments.contains(object)) { - mObsoleteFragments.remove(object); - return POSITION_NONE; - } - return super.getItemPosition(object); - } - - - @Override - public Object instantiateItem(ViewGroup container, int position) { - Object fragment = super.instantiateItem(container, position); - mCachedFragments.put(Integer.valueOf(position), (FileFragment)fragment); - return fragment; - } - - @Override - public void destroyItem(ViewGroup container, int position, Object object) { - mCachedFragments.remove(Integer.valueOf(position)); - super.destroyItem(container, position, object); - } - - - public boolean pendingErrorAt(int position) { - return mDownloadErrors.contains(Integer.valueOf(position)); - } - - /* -* - * Called when a change in the shown pages is going to start being made. - * - * @param container The containing View which is displaying this adapter's page views. - *- / - @Override - public void startUpdate(ViewGroup container) { - Log.e(TAG, "** startUpdate"); - } - - @Override - public Object instantiateItem(ViewGroup container, int position) { - Log.e(TAG, "** instantiateItem " + position); - - if (mFragments.size() > position) { - Fragment fragment = mFragments.get(position); - if (fragment != null) { - Log.e(TAG, "** \t returning cached item"); - return fragment; - } - } - - if (mCurTransaction == null) { - mCurTransaction = mFragmentManager.beginTransaction(); - } - - Fragment fragment = getItem(position); - if (mSavedState.size() > position) { - Fragment.SavedState savedState = mSavedState.get(position); - if (savedState != null) { - // TODO WATCH OUT: - // * The Fragment must currently be attached to the FragmentManager. - // * A new Fragment created using this saved state must be the same class type as the Fragment it was created from. - // * The saved state can not contain dependencies on other fragments -- that is it can't use putFragment(Bundle, String, Fragment) - // to store a fragment reference - fragment.setInitialSavedState(savedState); - } - } - while (mFragments.size() <= position) { - mFragments.add(null); - } - fragment.setMenuVisibility(false); - mFragments.set(position, fragment); - //Log.e(TAG, "** \t adding fragment at position " + position + ", containerId " + container.getId()); - mCurTransaction.add(container.getId(), fragment); - - return fragment; - } - - @Override - public void destroyItem(ViewGroup container, int position, Object object) { - Log.e(TAG, "** destroyItem " + position); - Fragment fragment = (Fragment)object; - - if (mCurTransaction == null) { - mCurTransaction = mFragmentManager.beginTransaction(); - } - Log.e(TAG, "** \t removing fragment at position " + position); - while (mSavedState.size() <= position) { - mSavedState.add(null); - } - mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment)); - mFragments.set(position, null); - - mCurTransaction.remove(fragment); - } - - @Override - public void setPrimaryItem(ViewGroup container, int position, Object object) { - Fragment fragment = (Fragment)object; - if (fragment != mCurrentPrimaryItem) { - if (mCurrentPrimaryItem != null) { - mCurrentPrimaryItem.setMenuVisibility(false); - } - if (fragment != null) { - fragment.setMenuVisibility(true); - } - mCurrentPrimaryItem = fragment; - } - } - - @Override - public void finishUpdate(ViewGroup container) { - Log.e(TAG, "** finishUpdate (start)"); - if (mCurTransaction != null) { - mCurTransaction.commitAllowingStateLoss(); - mCurTransaction = null; - mFragmentManager.executePendingTransactions(); - } - Log.e(TAG, "** finishUpdate (end)"); - } - - @Override - public boolean isViewFromObject(View view, Object object) { - return ((Fragment)object).getView() == view; - } - - @Override - public Parcelable saveState() { - Bundle state = null; - if (mSavedState.size() > 0) { - state = new Bundle(); - Fragment.SavedState[] savedStates = new Fragment.SavedState[mSavedState.size()]; - mSavedState.toArray(savedStates); - state.putParcelableArray("states", savedStates); - } - for (int i=0; i keys = bundle.keySet(); - for (String key: keys) { - if (key.startsWith("f")) { - int index = Integer.parseInt(key.substring(1)); - Fragment f = mFragmentManager.getFragment(bundle, key); - if (f != null) { - while (mFragments.size() <= index) { - mFragments.add(null); - } - f.setMenuVisibility(false); - mFragments.set(index, f); - } else { - Log.w(TAG, "Bad fragment at key " + key); - } - } - } - } - } - */ -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/preview/PreviewMediaFragment.java b/src/de/mobilcom/debitel/cloud/android/ui/preview/PreviewMediaFragment.java deleted file mode 100644 index 89af08f9..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/preview/PreviewMediaFragment.java +++ /dev/null @@ -1,769 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.preview; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import android.accounts.Account; -import android.app.Activity; -import android.app.AlertDialog; -import android.content.ActivityNotFoundException; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.res.Configuration; -import android.media.MediaPlayer; -import android.media.MediaPlayer.OnCompletionListener; -import android.media.MediaPlayer.OnErrorListener; -import android.media.MediaPlayer.OnPreparedListener; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnTouchListener; -import android.view.ViewGroup; -import android.webkit.MimeTypeMap; -import android.widget.ImageView; -import android.widget.Toast; -import android.widget.VideoView; - -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -import com.actionbarsherlock.view.MenuItem; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.datamodel.FileDataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.media.MediaControlView; -import de.mobilcom.debitel.cloud.android.media.MediaService; -import de.mobilcom.debitel.cloud.android.media.MediaServiceBinder; -import de.mobilcom.debitel.cloud.android.operations.OnRemoteOperationListener; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperation; -import de.mobilcom.debitel.cloud.android.operations.RemoteOperationResult; -import de.mobilcom.debitel.cloud.android.operations.RemoveFileOperation; -import de.mobilcom.debitel.cloud.android.ui.activity.FileActivity; -import de.mobilcom.debitel.cloud.android.ui.activity.FileDisplayActivity; -import de.mobilcom.debitel.cloud.android.ui.fragment.ConfirmationDialogFragment; -import de.mobilcom.debitel.cloud.android.ui.fragment.FileFragment; -import eu.alefzero.webdav.WebdavUtils; - -/** - * This fragment shows a preview of a downloaded media file (audio or video). - * - * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link Account} values will produce an {@link IllegalStateException}. - * - * By now, if the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on instantiation too. - * - * @author David A. Velasco - */ -public class PreviewMediaFragment extends FileFragment implements - OnTouchListener, - ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener { - - public static final String EXTRA_FILE = "FILE"; - public static final String EXTRA_ACCOUNT = "ACCOUNT"; - private static final String EXTRA_PLAY_POSITION = "PLAY_POSITION"; - private static final String EXTRA_PLAYING = "PLAYING"; - - private View mView; - private Account mAccount; - private FileDataStorageManager mStorageManager; - private ImageView mImagePreview; - private VideoView mVideoPreview; - private int mSavedPlaybackPosition; - - private Handler mHandler; - private RemoteOperation mLastRemoteOperation; - - private MediaServiceBinder mMediaServiceBinder = null; - private MediaControlView mMediaController = null; - private MediaServiceConnection mMediaServiceConnection = null; - private VideoHelper mVideoHelper; - private boolean mAutoplay; - public boolean mPrepared; - - private static final String TAG = PreviewMediaFragment.class.getSimpleName(); - - - /** - * Creates a fragment to preview a file. - * - * When 'fileToDetail' or 'ocAccount' are null - * - * @param fileToDetail An {@link OCFile} to preview in the fragment - * @param ocAccount An ownCloud account; needed to start downloads - */ - public PreviewMediaFragment(OCFile fileToDetail, Account ocAccount, int startPlaybackPosition, boolean autoplay) { - super(fileToDetail); - mAccount = ocAccount; - mSavedPlaybackPosition = startPlaybackPosition; - mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment - mAutoplay = autoplay; - } - - - /** - * Creates an empty fragment for previews. - * - * MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically (for instance, when the device is turned a aside). - * - * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction - */ - public PreviewMediaFragment() { - super(); - mAccount = null; - mSavedPlaybackPosition = 0; - mStorageManager = null; - mAutoplay = true; - } - - - /** - * {@inheritDoc} - */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mHandler = new Handler(); - setHasOptionsMenu(true); - } - - - /** - * {@inheritDoc} - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - super.onCreateView(inflater, container, savedInstanceState); - Log_OC.e(TAG, "onCreateView"); - - - mView = inflater.inflate(R.layout.file_preview, container, false); - - mImagePreview = (ImageView)mView.findViewById(R.id.image_preview); - mVideoPreview = (VideoView)mView.findViewById(R.id.video_preview); - mVideoPreview.setOnTouchListener(this); - - mMediaController = (MediaControlView)mView.findViewById(R.id.media_controller); - - return mView; - } - - - /** - * {@inheritDoc} - */ - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - Log_OC.e(TAG, "onAttach"); - - if (!(activity instanceof FileFragment.ContainerActivity)) - throw new ClassCastException(activity.toString() + " must implement " + FileFragment.ContainerActivity.class.getSimpleName()); - } - - - /** - * {@inheritDoc} - */ - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - Log_OC.e(TAG, "onActivityCreated"); - - mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver()); - if (savedInstanceState != null) { - setFile((OCFile)savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_FILE)); - mAccount = savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_ACCOUNT); - mSavedPlaybackPosition = savedInstanceState.getInt(PreviewMediaFragment.EXTRA_PLAY_POSITION); - mAutoplay = savedInstanceState.getBoolean(PreviewMediaFragment.EXTRA_PLAYING); - - } - OCFile file = getFile(); - if (file == null) { - throw new IllegalStateException("Instanced with a NULL OCFile"); - } - if (mAccount == null) { - throw new IllegalStateException("Instanced with a NULL ownCloud Account"); - } - if (!file.isDown()) { - throw new IllegalStateException("There is no local file to preview"); - } - if (file.isVideo()) { - mVideoPreview.setVisibility(View.VISIBLE); - mImagePreview.setVisibility(View.GONE); - prepareVideo(); - - } else { - mVideoPreview.setVisibility(View.GONE); - mImagePreview.setVisibility(View.VISIBLE); - } - - } - - - /** - * {@inheritDoc} - */ - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - Log_OC.e(TAG, "onSaveInstanceState"); - - outState.putParcelable(PreviewMediaFragment.EXTRA_FILE, getFile()); - outState.putParcelable(PreviewMediaFragment.EXTRA_ACCOUNT, mAccount); - - if (getFile().isVideo()) { - mSavedPlaybackPosition = mVideoPreview.getCurrentPosition(); - mAutoplay = mVideoPreview.isPlaying(); - outState.putInt(PreviewMediaFragment.EXTRA_PLAY_POSITION , mSavedPlaybackPosition); - outState.putBoolean(PreviewMediaFragment.EXTRA_PLAYING , mAutoplay); - } else { - outState.putInt(PreviewMediaFragment.EXTRA_PLAY_POSITION , mMediaServiceBinder.getCurrentPosition()); - outState.putBoolean(PreviewMediaFragment.EXTRA_PLAYING , mMediaServiceBinder.isPlaying()); - } - } - - - @Override - public void onStart() { - super.onStart(); - Log_OC.e(TAG, "onStart"); - - OCFile file = getFile(); - if (file != null) { - if (file.isAudio()) { - bindMediaService(); - - } else if (file.isVideo()) { - stopAudio(); - playVideo(); - } - } - } - - - private void stopAudio() { - Intent i = new Intent(getSherlockActivity(), MediaService.class); - i.setAction(MediaService.ACTION_STOP_ALL); - getSherlockActivity().startService(i); - } - - - /** - * {@inheritDoc} - */ - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - - inflater.inflate(R.menu.file_actions_menu, menu); - List toHide = new ArrayList(); - - MenuItem item = null; - toHide.add(R.id.action_cancel_download); - toHide.add(R.id.action_cancel_upload); - toHide.add(R.id.action_download_file); - toHide.add(R.id.action_sync_file); - toHide.add(R.id.action_rename_file); // by now - - for (int i : toHide) { - item = menu.findItem(i); - if (item != null) { - item.setVisible(false); - item.setEnabled(false); - } - } - - } - - - /** - * {@inheritDoc} - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_open_file_with: { - openFile(); - return true; - } - case R.id.action_remove_file: { - removeFile(); - return true; - } - case R.id.action_see_details: { - seeDetails(); - return true; - } - - default: - return false; - } - } - - - private void seeDetails() { - stopPreview(false); - ((FileFragment.ContainerActivity)getActivity()).showDetails(getFile()); - } - - - private void prepareVideo() { - // create helper to get more control on the playback - mVideoHelper = new VideoHelper(); - mVideoPreview.setOnPreparedListener(mVideoHelper); - mVideoPreview.setOnCompletionListener(mVideoHelper); - mVideoPreview.setOnErrorListener(mVideoHelper); - } - - private void playVideo() { - // create and prepare control panel for the user - mMediaController.setMediaPlayer(mVideoPreview); - - // load the video file in the video player ; when done, VideoHelper#onPrepared() will be called - mVideoPreview.setVideoPath(getFile().getStoragePath()); - } - - - private class VideoHelper implements OnCompletionListener, OnPreparedListener, OnErrorListener { - - /** - * Called when the file is ready to be played. - * - * Just starts the playback. - * - * @param mp {@link MediaPlayer} instance performing the playback. - */ - @Override - public void onPrepared(MediaPlayer vp) { - Log_OC.e(TAG, "onPrepared"); - mVideoPreview.seekTo(mSavedPlaybackPosition); - if (mAutoplay) { - mVideoPreview.start(); - } - mMediaController.setEnabled(true); - mMediaController.updatePausePlay(); - mPrepared = true; - } - - - /** - * Called when the file is finished playing. - * - * Finishes the activity. - * - * @param mp {@link MediaPlayer} instance performing the playback. - */ - @Override - public void onCompletion(MediaPlayer mp) { - Log_OC.e(TAG, "completed"); - if (mp != null) { - mVideoPreview.seekTo(0); - // next lines are necessary to work around undesired video loops - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.GINGERBREAD) { - mVideoPreview.pause(); - - } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.GINGERBREAD_MR1) { - // mVideePreview.pause() is not enough - - mMediaController.setEnabled(false); - mVideoPreview.stopPlayback(); - mAutoplay = false; - mSavedPlaybackPosition = 0; - mVideoPreview.setVideoPath(getFile().getStoragePath()); - } - } // else : called from onError() - mMediaController.updatePausePlay(); - } - - - /** - * Called when an error in playback occurs. - * - * @param mp {@link MediaPlayer} instance performing the playback. - * @param what Type of error - * @param extra Extra code specific to the error - */ - @Override - public boolean onError(MediaPlayer mp, int what, int extra) { - if (mVideoPreview.getWindowToken() != null) { - String message = MediaService.getMessageForMediaError(getActivity(), what, extra); - new AlertDialog.Builder(getActivity()) - .setMessage(message) - .setPositiveButton(android.R.string.VideoView_error_button, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - dialog.dismiss(); - VideoHelper.this.onCompletion(null); - } - }) - .setCancelable(false) - .show(); - } - return true; - } - - } - - - @Override - public void onPause() { - super.onPause(); - Log_OC.e(TAG, "onPause"); - } - - @Override - public void onResume() { - super.onResume(); - Log_OC.e(TAG, "onResume"); - } - - @Override - public void onDestroy() { - super.onDestroy(); - Log_OC.e(TAG, "onDestroy"); - } - - @Override - public void onStop() { - Log_OC.e(TAG, "onStop"); - super.onStop(); - - mPrepared = false; - if (mMediaServiceConnection != null) { - Log_OC.d(TAG, "Unbinding from MediaService ..."); - if (mMediaServiceBinder != null && mMediaController != null) { - mMediaServiceBinder.unregisterMediaController(mMediaController); - } - getActivity().unbindService(mMediaServiceConnection); - mMediaServiceConnection = null; - mMediaServiceBinder = null; - } - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN && v == mVideoPreview) { - startFullScreenVideo(); - return true; - } - return false; - } - - - private void startFullScreenVideo() { - Intent i = new Intent(getActivity(), PreviewVideoActivity.class); - i.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount); - i.putExtra(FileActivity.EXTRA_FILE, getFile()); - i.putExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, mVideoPreview.isPlaying()); - mVideoPreview.pause(); - i.putExtra(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPreview.getCurrentPosition()); - startActivityForResult(i, 0); - } - - @Override - public void onConfigurationChanged (Configuration newConfig) { - Log_OC.e(TAG, "onConfigurationChanged " + this); - } - - @Override - public void onActivityResult (int requestCode, int resultCode, Intent data) { - Log_OC.e(TAG, "onActivityResult " + this); - super.onActivityResult(requestCode, resultCode, data); - if (resultCode == Activity.RESULT_OK) { - mSavedPlaybackPosition = data.getExtras().getInt(PreviewVideoActivity.EXTRA_START_POSITION); - mAutoplay = data.getExtras().getBoolean(PreviewVideoActivity.EXTRA_AUTOPLAY); - } - } - - - private void playAudio() { - OCFile file = getFile(); - if (!mMediaServiceBinder.isPlaying(file)) { - Log_OC.d(TAG, "starting playback of " + file.getStoragePath()); - mMediaServiceBinder.start(mAccount, file, mAutoplay, mSavedPlaybackPosition); - - } else { - if (!mMediaServiceBinder.isPlaying() && mAutoplay) { - mMediaServiceBinder.start(); - mMediaController.updatePausePlay(); - } - } - } - - - private void bindMediaService() { - Log_OC.d(TAG, "Binding to MediaService..."); - if (mMediaServiceConnection == null) { - mMediaServiceConnection = new MediaServiceConnection(); - } - getActivity().bindService( new Intent(getActivity(), - MediaService.class), - mMediaServiceConnection, - Context.BIND_AUTO_CREATE); - // follow the flow in MediaServiceConnection#onServiceConnected(...) - } - - /** Defines callbacks for service binding, passed to bindService() */ - private class MediaServiceConnection implements ServiceConnection { - - @Override - public void onServiceConnected(ComponentName component, IBinder service) { - if (component.equals(new ComponentName(getActivity(), MediaService.class))) { - Log_OC.d(TAG, "Media service connected"); - mMediaServiceBinder = (MediaServiceBinder) service; - if (mMediaServiceBinder != null) { - prepareMediaController(); - playAudio(); // do not wait for the touch of nobody to play audio - - Log_OC.d(TAG, "Successfully bound to MediaService, MediaController ready"); - - } else { - Log_OC.e(TAG, "Unexpected response from MediaService while binding"); - } - } - } - - private void prepareMediaController() { - mMediaServiceBinder.registerMediaController(mMediaController); - if (mMediaController != null) { - mMediaController.setMediaPlayer(mMediaServiceBinder); - mMediaController.setEnabled(true); - mMediaController.updatePausePlay(); - } - } - - @Override - public void onServiceDisconnected(ComponentName component) { - if (component.equals(new ComponentName(getActivity(), MediaService.class))) { - Log_OC.e(TAG, "Media service suddenly disconnected"); - if (mMediaController != null) { - mMediaController.setMediaPlayer(null); - } else { - Toast.makeText(getActivity(), "No media controller to release when disconnected from media service", Toast.LENGTH_SHORT).show(); - } - mMediaServiceBinder = null; - mMediaServiceConnection = null; - } - } - } - - - - /** - * Opens the previewed file with an external application. - * - * TODO - improve this; instead of prioritize the actions available for the MIME type in the server, - * we should get a list of available apps for MIME tpye in the server and join it with the list of - * available apps for the MIME type known from the file extension, to let the user choose - */ - private void openFile() { - OCFile file = getFile(); - stopPreview(true); - String storagePath = file.getStoragePath(); - String encodedStoragePath = WebdavUtils.encodePath(storagePath); - try { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), file.getMimetype()); - i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - startActivity(i); - - } catch (Throwable t) { - Log_OC.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + file.getMimetype()); - boolean toastIt = true; - String mimeType = ""; - try { - Intent i = new Intent(Intent.ACTION_VIEW); - mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1)); - if (mimeType == null || !mimeType.equals(file.getMimetype())) { - if (mimeType != null) { - i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType); - } else { - // desperate try - i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*-/*"); - } - i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - startActivity(i); - toastIt = false; - } - - } catch (IndexOutOfBoundsException e) { - Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath); - - } catch (ActivityNotFoundException e) { - Log_OC.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension"); - - } catch (Throwable th) { - Log_OC.e(TAG, "Unexpected problem when opening: " + storagePath, th); - - } finally { - if (toastIt) { - Toast.makeText(getActivity(), "There is no application to handle file " + file.getFileName(), Toast.LENGTH_SHORT).show(); - } - } - - } - finish(); - } - - /** - * Starts a the removal of the previewed file. - * - * Shows a confirmation dialog. The action continues in {@link #onConfirmation(String)} , {@link #onNeutral(String)} or {@link #onCancel(String)}, - * depending upon the user selection in the dialog. - */ - private void removeFile() { - ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance( - R.string.confirmation_remove_alert, - new String[]{getFile().getFileName()}, - R.string.confirmation_remove_remote_and_local, - R.string.confirmation_remove_local, - R.string.common_cancel); - confDialog.setOnConfirmationListener(this); - confDialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION); - } - - - /** - * Performs the removal of the previewed file, both locally and in the server. - */ - @Override - public void onConfirmation(String callerTag) { - OCFile file = getFile(); - if (mStorageManager.getFileById(file.getFileId()) != null) { // check that the file is still there; - stopPreview(true); - mLastRemoteOperation = new RemoveFileOperation( file, // TODO we need to review the interface with RemoteOperations, and use OCFile IDs instead of OCFile objects as parameters - true, - mStorageManager); - mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); - - ((FileDisplayActivity) getActivity()).showLoadingDialog(); - } - } - - - /** - * Removes the file from local storage - */ - @Override - public void onNeutral(String callerTag) { - // TODO this code should be made in a secondary thread, - OCFile file = getFile(); - if (file.isDown()) { // checks it is still there - stopPreview(true); - File f = new File(file.getStoragePath()); - f.delete(); - file.setStoragePath(null); - mStorageManager.saveFile(file); - finish(); - } - } - - /** - * User cancelled the removal action. - */ - @Override - public void onCancel(String callerTag) { - // nothing to do here - } - - - /** - * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewMediaFragment} to be previewed. - * - * @param file File to test if can be previewed. - * @return 'True' if the file can be handled by the fragment. - */ - public static boolean canBePreviewed(OCFile file) { - return (file != null && (file.isAudio() || file.isVideo())); - } - - /** - * {@inheritDoc} - */ - @Override - public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { - if (operation.equals(mLastRemoteOperation)) { - if (operation instanceof RemoveFileOperation) { - onRemoveFileOperationFinish((RemoveFileOperation)operation, result); - } - } - } - - private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) { - ((FileDisplayActivity) getActivity()).dismissLoadingDialog(); - if (result.isSuccess()) { - Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG); - msg.show(); - finish(); - - } else { - Toast msg = Toast.makeText(getActivity(), R.string.remove_fail_msg, Toast.LENGTH_LONG); - msg.show(); - if (result.isSslRecoverableException()) { - // TODO show the SSL warning dialog - } - } - } - - private void stopPreview(boolean stopAudio) { - OCFile file = getFile(); - if (file.isAudio() && stopAudio) { - mMediaServiceBinder.pause(); - - } else if (file.isVideo()) { - mVideoPreview.stopPlayback(); - } - } - - - - /** - * Finishes the preview - */ - private void finish() { - getActivity().onBackPressed(); - } - - - public int getPosition() { - if (mPrepared) { - mSavedPlaybackPosition = mVideoPreview.getCurrentPosition(); - } - Log_OC.e(TAG, "getting position: " + mSavedPlaybackPosition); - return mSavedPlaybackPosition; - } - - public boolean isPlaying() { - if (mPrepared) { - mAutoplay = mVideoPreview.isPlaying(); - } - return mAutoplay; - } - -} diff --git a/src/de/mobilcom/debitel/cloud/android/ui/preview/PreviewVideoActivity.java b/src/de/mobilcom/debitel/cloud/android/ui/preview/PreviewVideoActivity.java deleted file mode 100644 index bedb806f..00000000 --- a/src/de/mobilcom/debitel/cloud/android/ui/preview/PreviewVideoActivity.java +++ /dev/null @@ -1,239 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.ui.preview; - -import android.accounts.Account; -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.media.MediaPlayer; -import android.media.MediaPlayer.OnCompletionListener; -import android.media.MediaPlayer.OnErrorListener; -import android.media.MediaPlayer.OnPreparedListener; -import android.net.Uri; -import android.os.Bundle; -import android.widget.MediaController; -import android.widget.VideoView; - -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils.AccountNotFoundException; -import de.mobilcom.debitel.cloud.android.datamodel.DataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.FileDataStorageManager; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; -import de.mobilcom.debitel.cloud.android.media.MediaService; -import de.mobilcom.debitel.cloud.android.ui.activity.FileActivity; - -/** - * Activity implementing a basic video player. - * - * Used as an utility to preview video files contained in an ownCloud account. - * - * Currently, it always plays in landscape mode, full screen. When the playback ends, - * the activity is finished. - * - * @author David A. Velasco - */ -public class PreviewVideoActivity extends FileActivity implements OnCompletionListener, OnPreparedListener, OnErrorListener { - - /** Key to receive a flag signaling if the video should be started immediately */ - public static final String EXTRA_AUTOPLAY = "AUTOPLAY"; - - /** Key to receive the position of the playback where the video should be put at start */ - public static final String EXTRA_START_POSITION = "START_POSITION"; - - private static final String TAG = PreviewVideoActivity.class.getSimpleName(); - - private DataStorageManager mStorageManager; - - private int mSavedPlaybackPosition; // in the unit time handled by MediaPlayer.getCurrentPosition() - private boolean mAutoplay; // when 'true', the playback starts immediately with the activity - private VideoView mVideoPlayer; // view to play the file; both performs and show the playback - private MediaController mMediaController; // panel control used by the user to control the playback - - /** - * Called when the activity is first created. - * - * Searches for an {@link OCFile} and ownCloud {@link Account} holding it in the starting {@link Intent}. - * - * The {@link Account} is unnecessary if the file is downloaded; else, the {@link Account} is used to - * try to stream the remote file - TODO get the streaming works - * - * {@inheritDoc} - */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Log_OC.e(TAG, "ACTIVITY\t\tonCreate"); - - setContentView(R.layout.video_layout); - - if (savedInstanceState == null) { - Bundle extras = getIntent().getExtras(); - mSavedPlaybackPosition = extras.getInt(EXTRA_START_POSITION); - mAutoplay = extras.getBoolean(EXTRA_AUTOPLAY); - - } else { - mSavedPlaybackPosition = savedInstanceState.getInt(EXTRA_START_POSITION); - mAutoplay = savedInstanceState.getBoolean(EXTRA_AUTOPLAY); - } - - mVideoPlayer = (VideoView) findViewById(R.id.videoPlayer); - - // set listeners to get more contol on the playback - mVideoPlayer.setOnPreparedListener(this); - mVideoPlayer.setOnCompletionListener(this); - mVideoPlayer.setOnErrorListener(this); - - // keep the screen on while the playback is performed (prevents screen off by battery save) - mVideoPlayer.setKeepScreenOn(true); - } - - - /** - * {@inheritDoc} - */ - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - Log_OC.e(TAG, "ACTIVITY\t\tonSaveInstanceState"); - outState.putInt(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPlayer.getCurrentPosition()); - outState.putBoolean(PreviewVideoActivity.EXTRA_AUTOPLAY , mVideoPlayer.isPlaying()); - } - - - @Override - public void onBackPressed() { - Log_OC.e(TAG, "ACTIVTIY\t\tonBackPressed"); - Intent i = new Intent(); - i.putExtra(EXTRA_AUTOPLAY, mVideoPlayer.isPlaying()); - i.putExtra(EXTRA_START_POSITION, mVideoPlayer.getCurrentPosition()); - setResult(RESULT_OK, i); - super.onBackPressed(); - } - - - /** - * Called when the file is ready to be played. - * - * Just starts the playback. - * - * @param mp {@link MediaPlayer} instance performing the playback. - */ - @Override - public void onPrepared(MediaPlayer mp) { - Log_OC.e(TAG, "ACTIVITY\t\tonPrepare"); - mVideoPlayer.seekTo(mSavedPlaybackPosition); - if (mAutoplay) { - mVideoPlayer.start(); - } - mMediaController.show(5000); - } - - - /** - * Called when the file is finished playing. - * - * Rewinds the video - * - * @param mp {@link MediaPlayer} instance performing the playback. - */ - @Override - public void onCompletion(MediaPlayer mp) { - mVideoPlayer.seekTo(0); - } - - - /** - * Called when an error in playback occurs. - * - * @param mp {@link MediaPlayer} instance performing the playback. - * @param what Type of error - * @param extra Extra code specific to the error - */ - @Override - public boolean onError(MediaPlayer mp, int what, int extra) { - Log_OC.e(TAG, "Error in video playback, what = " + what + ", extra = " + extra); - - if (mMediaController != null) { - mMediaController.hide(); - } - - if (mVideoPlayer.getWindowToken() != null) { - String message = MediaService.getMessageForMediaError(this, what, extra); - new AlertDialog.Builder(this) - .setMessage(message) - .setPositiveButton(android.R.string.VideoView_error_button, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - PreviewVideoActivity.this.onCompletion(null); - } - }) - .setCancelable(false) - .show(); - } - return true; - } - - - @Override - protected void onAccountSet(boolean stateWasRecovered) { - if (getAccount() != null) { - OCFile file = getFile(); - /// Validate handled file (first image to preview) - if (file == null) { - throw new IllegalStateException("Instanced with a NULL OCFile"); - } - if (!file.isVideo()) { - throw new IllegalArgumentException("Non-video file passed as argument"); - } - mStorageManager = new FileDataStorageManager(getAccount(), getContentResolver()); - file = mStorageManager.getFileById(file.getFileId()); - if (file != null) { - if (file.isDown()) { - mVideoPlayer.setVideoPath(file.getStoragePath()); - - } else { - // not working yet - String url; - try { - url = AccountUtils.constructFullURLForAccount(this, getAccount()) + file.getRemotePath(); - mVideoPlayer.setVideoURI(Uri.parse(url)); - } catch (AccountNotFoundException e) { - onError(null, MediaService.OC_MEDIA_ERROR, R.string.media_err_no_account); - } - } - - // create and prepare control panel for the user - mMediaController = new MediaController(this); - mMediaController.setMediaPlayer(mVideoPlayer); - mMediaController.setAnchorView(mVideoPlayer); - mVideoPlayer.setMediaController(mMediaController); - - } else { - finish(); - } - } else { - Log_OC.wtf(TAG, "onAccountChanged was called with NULL account associated!"); - finish(); - } - } - - -} \ No newline at end of file diff --git a/src/de/mobilcom/debitel/cloud/android/utils/FileStorageUtils.java b/src/de/mobilcom/debitel/cloud/android/utils/FileStorageUtils.java deleted file mode 100644 index 1608a995..00000000 --- a/src/de/mobilcom/debitel/cloud/android/utils/FileStorageUtils.java +++ /dev/null @@ -1,79 +0,0 @@ -/* ownCloud Android client application - * 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 de.mobilcom.debitel.cloud.android.utils; - -import java.io.File; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.net.Uri; -import android.os.Environment; -import android.os.StatFs; - -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.R; -import de.mobilcom.debitel.cloud.android.datamodel.OCFile; - -/** - * Static methods to help in access to local file system. - * - * @author David A. Velasco - */ -public class FileStorageUtils { - //private static final String LOG_TAG = "FileStorageUtils"; - - 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 - } - - public static final String getDefaultSavePathFor(String accountName, OCFile file) { - return getSavePath(accountName) + file.getRemotePath(); - } - - 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 - } - - @SuppressLint("NewApi") - public static final long getUsableSpace(String accountName) { - File savePath = Environment.getExternalStorageDirectory(); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD) { - return savePath.getUsableSpace(); - - } else { - StatFs stats = new StatFs(savePath.getAbsolutePath()); - return stats.getAvailableBlocks() * stats.getBlockSize(); - } - - } - - public static final String getLogPath() { - return Environment.getExternalStorageDirectory() + File.separator + MainApp.getDataFolder() + File.separator + "log"; - } - - public static String getInstantUploadFilePath(Context context, String fileName) { - String uploadPath = context.getString(R.string.instant_upload_path); - String value = uploadPath + OCFile.PATH_SEPARATOR + (fileName == null ? "" : fileName); - return value; - } - -} \ No newline at end of file diff --git a/src/de/mobilcom/debitel/cloud/android/utils/OwnCloudVersion.java b/src/de/mobilcom/debitel/cloud/android/utils/OwnCloudVersion.java deleted file mode 100644 index e77f90f5..00000000 --- a/src/de/mobilcom/debitel/cloud/android/utils/OwnCloudVersion.java +++ /dev/null @@ -1,85 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.utils; - -public class OwnCloudVersion implements Comparable { - public static final OwnCloudVersion owncloud_v1 = new OwnCloudVersion( - 0x010000); - public static final OwnCloudVersion owncloud_v2 = new OwnCloudVersion( - 0x020000); - public static final OwnCloudVersion owncloud_v3 = new OwnCloudVersion( - 0x030000); - public static final OwnCloudVersion owncloud_v4 = new OwnCloudVersion( - 0x040000); - public static final OwnCloudVersion owncloud_v4_5 = new OwnCloudVersion( - 0x040500); - - // format is in version - // 0xAABBCC - // for version AA.BB.CC - // ie version 2.0.3 will be stored as 0x030003 - private int mVersion; - private boolean mIsValid; - - public OwnCloudVersion(int version) { - mVersion = version; - mIsValid = true; - } - - public OwnCloudVersion(String version) { - mVersion = 0; - mIsValid = false; - parseVersionString(version); - } - - public String toString() { - return ((mVersion >> 16) % 256) + "." + ((mVersion >> 8) % 256) + "." - + ((mVersion) % 256); - } - - public boolean isVersionValid() { - return mIsValid; - } - - @Override - public int compareTo(OwnCloudVersion another) { - return another.mVersion == mVersion ? 0 - : another.mVersion < mVersion ? 1 : -1; - } - - private void parseVersionString(String version) { - try { - String[] nums = version.split("\\."); - if (nums.length > 0) { - mVersion += Integer.parseInt(nums[0]); - } - mVersion = mVersion << 8; - if (nums.length > 1) { - mVersion += Integer.parseInt(nums[1]); - } - mVersion = mVersion << 8; - if (nums.length > 2) { - mVersion += Integer.parseInt(nums[2]); - } - mIsValid = true; - } catch (Exception e) { - mIsValid = false; - } - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/utils/RecursiveFileObserver.java b/src/de/mobilcom/debitel/cloud/android/utils/RecursiveFileObserver.java deleted file mode 100644 index 84c9bfdb..00000000 --- a/src/de/mobilcom/debitel/cloud/android/utils/RecursiveFileObserver.java +++ /dev/null @@ -1,101 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.utils; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Stack; - -import android.os.FileObserver; - -public class RecursiveFileObserver extends FileObserver { - - public static int CHANGES_ONLY = CLOSE_WRITE | MOVE_SELF | MOVED_FROM; - - List mObservers; - String mPath; - int mMask; - - public RecursiveFileObserver(String path) { - this(path, ALL_EVENTS); - } - - public RecursiveFileObserver(String path, int mask) { - super(path, mask); - mPath = path; - mMask = mask; - } - - @Override - public void startWatching() { - if (mObservers != null) return; - mObservers = new ArrayList(); - Stack stack = new Stack(); - stack.push(mPath); - - while (!stack.empty()) { - String parent = stack.pop(); - mObservers.add(new SingleFileObserver(parent, mMask)); - File path = new File(parent); - File[] files = path.listFiles(); - if (files == null) continue; - for (int i = 0; i < files.length; ++i) { - if (files[i].isDirectory() && !files[i].getName().equals(".") - && !files[i].getName().equals("..")) { - stack.push(files[i].getPath()); - } - } - } - for (int i = 0; i < mObservers.size(); i++) - mObservers.get(i).startWatching(); - } - - @Override - public void stopWatching() { - if (mObservers == null) return; - - for (int i = 0; i < mObservers.size(); ++i) - mObservers.get(i).stopWatching(); - - mObservers.clear(); - mObservers = null; - } - - @Override - public void onEvent(int event, String path) { - - } - - private class SingleFileObserver extends FileObserver { - private String mPath; - - public SingleFileObserver(String path, int mask) { - super(path, mask); - mPath = path; - } - - @Override - public void onEvent(int event, String path) { - String newPath = mPath + "/" + path; - RecursiveFileObserver.this.onEvent(event, newPath); - } - - } -} diff --git a/src/de/mobilcom/debitel/cloud/android/widgets/ActionEditText.java b/src/de/mobilcom/debitel/cloud/android/widgets/ActionEditText.java deleted file mode 100644 index 039ca3af..00000000 --- a/src/de/mobilcom/debitel/cloud/android/widgets/ActionEditText.java +++ /dev/null @@ -1,145 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.widgets; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import de.mobilcom.debitel.cloud.android.R; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.widget.EditText; - -public class ActionEditText extends EditText { - private String s; - private String optionOneString; - private int optionOneColor; - private String optionTwoString; - private int optionTwoColor; - private Rect mTextBounds, mButtonRect; - - private String badgeClickCallback; - private Rect btn_rect; - - public ActionEditText(Context context, AttributeSet attrs) { - super(context, attrs); - getAttrs(attrs); - s = optionOneString; - mTextBounds = new Rect(); - mButtonRect = new Rect(); - } - - public ActionEditText(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - getAttrs(attrs); - s = optionOneString; - mTextBounds = new Rect(); - mButtonRect = new Rect(); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - Paint p = getPaint(); - - p.getTextBounds(s, 0, s.length(), mTextBounds); - - getDrawingRect(mButtonRect); - mButtonRect.top += 10; - mButtonRect.bottom -= 10; - mButtonRect.left = (int) (getWidth() - mTextBounds.width() - 18); - mButtonRect.right = getWidth() - 10; - btn_rect = mButtonRect; - - if (s.equals(optionOneString)) - p.setColor(optionOneColor); - else - p.setColor(optionTwoColor); - canvas.drawRect(mButtonRect, p); - p.setColor(Color.GRAY); - - canvas.drawText(s, mButtonRect.left + 3, mButtonRect.bottom - - (mTextBounds.height() / 2), p); - - invalidate(); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - int touchX = (int) event.getX(); - int touchY = (int) event.getY(); - boolean r = super.onTouchEvent(event); - if (event.getAction() == MotionEvent.ACTION_UP) { - if (btn_rect.contains(touchX, touchY)) { - if (s.equals(optionTwoString)) - s = optionOneString; - else - s = optionTwoString; - if (badgeClickCallback != null) { - @SuppressWarnings("rawtypes") - Class[] paramtypes = new Class[2]; - paramtypes[0] = android.view.View.class; - paramtypes[1] = String.class; - Method method; - try { - - method = getContext().getClass().getMethod( - badgeClickCallback, paramtypes); - method.invoke(getContext(), this, s); - - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - - invalidate(); - } - } - } - return r; - } - - private void getAttrs(AttributeSet attr) { - TypedArray a = getContext().obtainStyledAttributes(attr, - R.styleable.ActionEditText); - optionOneString = a - .getString(R.styleable.ActionEditText_optionOneString); - optionTwoString = a - .getString(R.styleable.ActionEditText_optionTwoString); - optionOneColor = a.getColor(R.styleable.ActionEditText_optionOneColor, - 0x00ff00); - optionTwoColor = a.getColor(R.styleable.ActionEditText_optionTwoColor, - 0xff0000); - badgeClickCallback = a - .getString(R.styleable.ActionEditText_onBadgeClick); - } - -} diff --git a/src/eu/alefzero/webdav/ChunkFromFileChannelRequestEntity.java b/src/eu/alefzero/webdav/ChunkFromFileChannelRequestEntity.java index 86202479..a91b64ea 100644 --- a/src/eu/alefzero/webdav/ChunkFromFileChannelRequestEntity.java +++ b/src/eu/alefzero/webdav/ChunkFromFileChannelRequestEntity.java @@ -29,8 +29,9 @@ import java.util.Set; import org.apache.commons.httpclient.methods.RequestEntity; -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.network.ProgressiveDataTransferer; +import com.owncloud.android.Log_OC; +import com.owncloud.android.network.ProgressiveDataTransferer; + import eu.alefzero.webdav.OnDatatransferProgressListener; diff --git a/src/eu/alefzero/webdav/FileRequestEntity.java b/src/eu/alefzero/webdav/FileRequestEntity.java index 9754ef67..1d525c43 100644 --- a/src/eu/alefzero/webdav/FileRequestEntity.java +++ b/src/eu/alefzero/webdav/FileRequestEntity.java @@ -31,8 +31,9 @@ import java.util.Set; import org.apache.commons.httpclient.methods.RequestEntity; -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.network.ProgressiveDataTransferer; +import com.owncloud.android.Log_OC; +import com.owncloud.android.network.ProgressiveDataTransferer; + import eu.alefzero.webdav.OnDatatransferProgressListener; diff --git a/src/eu/alefzero/webdav/WebdavClient.java b/src/eu/alefzero/webdav/WebdavClient.java index 8c54a53d..6cb4b83e 100644 --- a/src/eu/alefzero/webdav/WebdavClient.java +++ b/src/eu/alefzero/webdav/WebdavClient.java @@ -41,11 +41,12 @@ import org.apache.commons.httpclient.params.HttpMethodParams; import org.apache.http.HttpStatus; import org.apache.http.params.CoreProtocolPNames; +import com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; +import com.owncloud.android.network.BearerAuthScheme; +import com.owncloud.android.network.BearerCredentials; + -import de.mobilcom.debitel.cloud.android.Log_OC; -import de.mobilcom.debitel.cloud.android.MainApp; -import de.mobilcom.debitel.cloud.android.network.BearerAuthScheme; -import de.mobilcom.debitel.cloud.android.network.BearerCredentials; import android.net.Uri; diff --git a/src/eu/alefzero/webdav/WebdavEntry.java b/src/eu/alefzero/webdav/WebdavEntry.java index 3ed89389..bdf1b35a 100644 --- a/src/eu/alefzero/webdav/WebdavEntry.java +++ b/src/eu/alefzero/webdav/WebdavEntry.java @@ -23,7 +23,8 @@ import org.apache.jackrabbit.webdav.property.DavProperty; import org.apache.jackrabbit.webdav.property.DavPropertyName; import org.apache.jackrabbit.webdav.property.DavPropertySet; -import de.mobilcom.debitel.cloud.android.Log_OC; +import com.owncloud.android.Log_OC; + import android.net.Uri; diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml index f78829df..b6312efd 100644 --- a/tests/AndroidManifest.xml +++ b/tests/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:targetPackage="com.owncloud.android" + android:label="Tests for com.owncloud.android"/> diff --git a/tests/src/com/owncloud/android/test/AccountUtilsTest.java b/tests/src/com/owncloud/android/test/AccountUtilsTest.java new file mode 100644 index 00000000..0af91839 --- /dev/null +++ b/tests/src/com/owncloud/android/test/AccountUtilsTest.java @@ -0,0 +1,58 @@ +/* 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.test; + +import com.owncloud.android.authentication.Accocom.owncloud.androiddroid.utils.OwnCloudVersion; + +importcom.owncloud.android + +public class AccountUtilsTest extends AndroidTestCase { + + public void testGetWebdavPathAndOCVersion() { + OwnCloudVersion ocv12 = new OwnCloudVersion(0x010200); + OwnCloudVersion ocv12s = new OwnCloudVersion("1.2"); + OwnCloudVersion ocv22 = new OwnCloudVersion(0x020200); + OwnCloudVersion ocv30 = new OwnCloudVersion(0x030000); + OwnCloudVersion ocv33s = new OwnCloudVersion("3.3.3"); + OwnCloudVersion ocv45 = new OwnCloudVersion(0x040500); + OwnCloudVersion ocv70 = new OwnCloudVersion(0x070000); + + assertTrue(AccountUtils.getWebdavPath(ocv12, false, false).equals("/webdav/owncloud.php")); + assertTrue(AccountUtils.getWebdavPath(ocv12s, false, false).equals("/webdav/owncloud.php")); + assertTrue(AccountUtils.getWebdavPath(ocv22, false, false).equals("/files/webdav.php")); + assertTrue(AccountUtils.getWebdavPath(ocv30,false, false).equals("/files/webdav.php")); + assertTrue(AccountUtils.getWebdavPath(ocv33s, false, false).equals("/files/webdav.php")); + assertTrue(AccountUtils.getWebdavPath(ocv45, false, false).equals("/remote.php/webdav")); + assertTrue(AccountUtils.getWebdavPath(ocv70, false, false).equals("/remote.php/webdav")); + assertNull(AccountUtils.getWebdavPath(null, false, false)); + assertTrue(AccountUtils.getWebdavPath(ocv12, true, false).equals("/remote.php/odav")); + assertTrue(AccountUtils.getWebdavPath(ocv12s, true, false).equals("/remote.php/odav")); + assertTrue(AccountUtils.getWebdavPath(ocv22, true, false).equals("/remote.php/odav")); + assertTrue(AccountUtils.getWebdavPath(ocv30, true, false).equals("/remote.php/odav")); + assertTrue(AccountUtils.getWebdavPath(ocv33s, true, false).equals("/remote.php/odav")); + assertTrue(AccountUtils.getWebdavPath(ocv45, true, false).equals("/remote.php/odav")); + assertTrue(AccountUtils.getWebdavPath(ocv70, true, false).equals("/remote.php/odav")); + + OwnCloudVersion invalidVer = new OwnCloudVersion("a.b.c"); + assertFalse(invalidVer.isVersionValid()); + + assertTrue(ocv45.toString().equals("4.5.0")); + } + +} diff --git a/tests/src/com/owncloud/android/test/FileContentProviderTest.java b/tests/src/com/owncloud/android/test/FileContentProviderTest.java new file mode 100644 index 00000000..f68dc237 --- /dev/null +++ b/tests/src/com/owncloud/android/test/FileContentProviderTest.java @@ -0,0 +1,52 @@ +package com.owncloud.android.test; + +import com.owncloud.androideta.ProviderTableMeta; +import com.owncloud.com.owncloud.androidider; + +import android.annotation.TargetApi; +import android.net.Uri; +import android.os.Build; +import android.test.ProviderTestCase2; +import android.test.mock.MockContentResolver; +import android.util.Log; + +@TargetApi(Build.VERSION_CODES.CUPCAKE) +public class FileContentProviderTest extends ProviderTestCase2 { + + private static final String TAG = FileContentProvider.class.getName(); + + private static MockContentResolver resolve; + + public FileContentProviderTest(Class providerClass, + String providerAuthority) { + super(providerClass, providerAuthority); + // TODO Auto-generated constructor stub + } + + public FileContentProviderTest() { + super(FileContentProvider.class, "com.owncloud.android.provicom.owncloud.android + @Override + public void setUp() { + Log.i(TAG, "Entered setup"); + try { + super.setUp(); + resolve = this.getMockContentResolver(); + } catch (Exception e) { + + } + } + + public void testGetTypeFile() { + Uri testuri = Uri.parse("content://org.owncloud/file/"); + assertEquals(ProviderTableMeta.CONTENT_TYPE_ITEM, resolve.getType(testuri)); + + testuri = Uri.parse("content://org.owncloud/file/123"); + assertEquals(ProviderTableMeta.CONTENT_TYPE_ITEM, resolve.getType(testuri)); + } + + public void testGetTypeRoot() { + Uri testuri = Uri.parse("content://org.owncloud/"); + assertEquals(ProviderTableMeta.CONTENT_TYPE, resolve.getType(testuri)); + } + +} diff --git a/tests/src/de/mobilcom/debitel/cloud/android/test/AccountUtilsTest.java b/tests/src/de/mobilcom/debitel/cloud/android/test/AccountUtilsTest.java deleted file mode 100644 index 976b3b32..00000000 --- a/tests/src/de/mobilcom/debitel/cloud/android/test/AccountUtilsTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* 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 de.mobilcom.debitel.cloud.android.test; - -import android.test.AndroidTestCase; - -import de.mobilcom.debitel.cloud.android.authentication.AccountUtils; -import de.mobilcom.debitel.cloud.android.utils.OwnCloudVersion; - -public class AccountUtilsTest extends AndroidTestCase { - - public void testGetWebdavPathAndOCVersion() { - OwnCloudVersion ocv12 = new OwnCloudVersion(0x010200); - OwnCloudVersion ocv12s = new OwnCloudVersion("1.2"); - OwnCloudVersion ocv22 = new OwnCloudVersion(0x020200); - OwnCloudVersion ocv30 = new OwnCloudVersion(0x030000); - OwnCloudVersion ocv33s = new OwnCloudVersion("3.3.3"); - OwnCloudVersion ocv45 = new OwnCloudVersion(0x040500); - OwnCloudVersion ocv70 = new OwnCloudVersion(0x070000); - - assertTrue(AccountUtils.getWebdavPath(ocv12, false, false).equals("/webdav/owncloud.php")); - assertTrue(AccountUtils.getWebdavPath(ocv12s, false, false).equals("/webdav/owncloud.php")); - assertTrue(AccountUtils.getWebdavPath(ocv22, false, false).equals("/files/webdav.php")); - assertTrue(AccountUtils.getWebdavPath(ocv30,false, false).equals("/files/webdav.php")); - assertTrue(AccountUtils.getWebdavPath(ocv33s, false, false).equals("/files/webdav.php")); - assertTrue(AccountUtils.getWebdavPath(ocv45, false, false).equals("/remote.php/webdav")); - assertTrue(AccountUtils.getWebdavPath(ocv70, false, false).equals("/remote.php/webdav")); - assertNull(AccountUtils.getWebdavPath(null, false, false)); - assertTrue(AccountUtils.getWebdavPath(ocv12, true, false).equals("/remote.php/odav")); - assertTrue(AccountUtils.getWebdavPath(ocv12s, true, false).equals("/remote.php/odav")); - assertTrue(AccountUtils.getWebdavPath(ocv22, true, false).equals("/remote.php/odav")); - assertTrue(AccountUtils.getWebdavPath(ocv30, true, false).equals("/remote.php/odav")); - assertTrue(AccountUtils.getWebdavPath(ocv33s, true, false).equals("/remote.php/odav")); - assertTrue(AccountUtils.getWebdavPath(ocv45, true, false).equals("/remote.php/odav")); - assertTrue(AccountUtils.getWebdavPath(ocv70, true, false).equals("/remote.php/odav")); - - OwnCloudVersion invalidVer = new OwnCloudVersion("a.b.c"); - assertFalse(invalidVer.isVersionValid()); - - assertTrue(ocv45.toString().equals("4.5.0")); - } - -} diff --git a/tests/src/de/mobilcom/debitel/cloud/android/test/FileContentProviderTest.java b/tests/src/de/mobilcom/debitel/cloud/android/test/FileContentProviderTest.java deleted file mode 100644 index 21daef03..00000000 --- a/tests/src/de/mobilcom/debitel/cloud/android/test/FileContentProviderTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package de.mobilcom.debitel.cloud.android.test; - -import de.mobilcom.debitel.cloud.android.db.ProviderMeta.ProviderTableMeta; -import de.mobilcom.debitel.cloud.android.providers.FileContentProvider; - -import android.annotation.TargetApi; -import android.net.Uri; -import android.os.Build; -import android.test.ProviderTestCase2; -import android.test.mock.MockContentResolver; -import android.util.Log; - -@TargetApi(Build.VERSION_CODES.CUPCAKE) -public class FileContentProviderTest extends ProviderTestCase2 { - - private static final String TAG = FileContentProvider.class.getName(); - - private static MockContentResolver resolve; - - public FileContentProviderTest(Class providerClass, - String providerAuthority) { - super(providerClass, providerAuthority); - // TODO Auto-generated constructor stub - } - - public FileContentProviderTest() { - super(FileContentProvider.class, "de.mobilcom.debitel.cloud.android.providers.FileContentProvider"); - } - - @Override - public void setUp() { - Log.i(TAG, "Entered setup"); - try { - super.setUp(); - resolve = this.getMockContentResolver(); - } catch (Exception e) { - - } - } - - public void testGetTypeFile() { - Uri testuri = Uri.parse("content://org.owncloud/file/"); - assertEquals(ProviderTableMeta.CONTENT_TYPE_ITEM, resolve.getType(testuri)); - - testuri = Uri.parse("content://org.owncloud/file/123"); - assertEquals(ProviderTableMeta.CONTENT_TYPE_ITEM, resolve.getType(testuri)); - } - - public void testGetTypeRoot() { - Uri testuri = Uri.parse("content://org.owncloud/"); - assertEquals(ProviderTableMeta.CONTENT_TYPE, resolve.getType(testuri)); - } - -}