- <?xml version="1.0" encoding="utf-8"?>\r
- <!-- \r
- ownCloud Android client application\r
- \r
- Copyright (C) 2012 Bartek Przybylski\r
- Copyright (C) 2012-2013 ownCloud Inc.\r
- \r
- This program is free software: you can redistribute it and/or modify\r
- it under the terms of the GNU General Public License as published by\r
- the Free Software Foundation, either version 2 of the License, or\r
- (at your option) any later version.\r
- \r
- This program is distributed in the hope that it will be useful,\r
- but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- GNU General Public License for more details.\r
- \r
- You should have received a copy of the GNU General Public License\r
- along with this program. If not, see <http://www.gnu.org/licenses/>.\r
- -->\r
- <manifest package="com.owncloud.android"\r
- android:versionCode="103022"\r
- android:versionName="1.3.22" xmlns:android="http://schemas.android.com/apk/res/android">\r
- \r
- <uses-permission android:name="android.permission.GET_ACCOUNTS" />\r
- <uses-permission android:name="android.permission.USE_CREDENTIALS" />\r
- <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />\r
- <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />\r
- <uses-permission android:name="android.permission.INTERNET" />\r
- <uses-permission android:name="android.permission.WRITE_SETTINGS" />\r
- <uses-permission android:name="android.permission.READ_SYNC_STATS" />\r
- <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />\r
- <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />\r
- <uses-permission android:name="android.permission.BROADCAST_STICKY" />\r
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />\r
- <uses-permission android:name="android.permission.READ_PHONE_STATE" />\r
- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>\r
- \r
- <uses-sdk\r
- android:minSdkVersion="8"\r
- android:targetSdkVersion="13" />\r
- \r
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" >\r
- </uses-permission>\r
- \r
- <application\r
- android:icon="@drawable/icon"\r
- android:label="@string/app_name"\r
- android:theme="@style/Theme.ownCloud"> \r
- <activity\r
- android:name=".ui.activity.FileDisplayActivity"\r
- android:label="@string/app_name">\r
- <intent-filter>\r
- <action android:name="android.intent.action.MAIN" />\r
- \r
- <category android:name="android.intent.category.LAUNCHER" />\r
- </intent-filter>\r
- </activity>\r
- <activity android:name=".ui.activity.UploadFilesActivity">\r
- </activity>\r
- <activity android:name=".Uploader" >\r
- <intent-filter>\r
- <action android:name="android.intent.action.SEND" >\r
- </action>\r
- \r
- <category android:name="android.intent.category.DEFAULT" >\r
- </category>\r
- \r
- <data android:mimeType="*/*" >\r
- </data>\r
- </intent-filter>\r
- <intent-filter>\r
- <action android:name="android.intent.action.SEND_MULTIPLE" >\r
- </action>\r
- \r
- <category android:name="android.intent.category.DEFAULT" >\r
- </category>\r
- \r
- <data android:mimeType="*/*" >\r
- </data>\r
- </intent-filter>\r
- </activity>\r
- <activity\r
- android:name=".ui.activity.Preferences"\r
- android:theme="@style/Theme.ownCloud" >\r
- </activity>\r
- <activity android:name=".ui.activity.PreferencesNewSessionewSession" >\r
- </activity>\r
- \r
- <service\r
- android:name=".authentication.AccountAuthenticatorService"\r
- android:exported="true">\r
- <intent-filter android:priority="100">\r
- <action android:name="android.accounts.AccountAuthenticator" />\r
- </intent-filter>\r
- \r
- <meta-data\r
- android:name="android.accounts.AccountAuthenticator"\r
- android:resource="@xml/authenticator" />\r
- </service>\r
- <service\r
- android:name=".syncadapter.FileSyncService"\r
- android:exported="true" >\r
- <intent-filter>\r
- <action android:name="android.content.SyncAdapter" />\r
- </intent-filter>\r
- \r
- <meta-data\r
- android:name="android.content.SyncAdapter"\r
- android:resource="@xml/syncadapter_files" />\r
- </service>\r
- \r
- <provider\r
- android:name=".providers.FileContentProvider"\r
- android:authorities="org.owncloud"\r
- android:enabled="true"\r
- android:exported="false"\r
- android:label="@string/sync_string_files"\r
- android:syncable="true" >\r
- </provider>\r
- \r
- <activity\r
- android:name=".authentication.AuthenticatorActivity"\r
- android:exported="true"\r
- android:theme="@style/Theme.ownCloud.noActionBar" \r
- android:launchMode="singleTask">\r
- <intent-filter>\r
- <action android:name="android.intent.action.VIEW" />\r
- <category android:name="android.intent.category.DEFAULT" />\r
- <category android:name="android.intent.category.BROWSABLE" />\r
- <data android:scheme="@string/oauth2_redirect_scheme" />\r
- </intent-filter>\r
- <intent-filter>\r
- <action android:name="com.owncloud.android.workaround.accounts.CREATE" />\r
- <category android:name="android.intent.category.DEFAULT" />\r
- </intent-filter>\r
- </activity>\r
- \r
- <service android:name=".files.services.FileDownloader" >\r
- </service>\r
- \r
+ <?xml version="1.0" encoding="utf-8"?>
+ <!--
+ 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 <http://www.gnu.org/licenses/>.
+ -->
+ <manifest package="com.owncloud.android"
+ android:versionCode="104000"
+ android:versionName="1.4.0" xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+ <uses-permission android:name="android.permission.USE_CREDENTIALS" />
+ <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
+ <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.READ_SYNC_STATS" />
+ <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+ <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
+
+ <uses-sdk
+ android:minSdkVersion="8"
+ android:targetSdkVersion="13" />
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" >
+ </uses-permission>
+
+ <application
+ android:icon="@drawable/icon"
+ android:label="@string/app_name"
+ android:theme="@style/Theme.ownCloud">
+ <activity
+ android:name=".ui.activity.FileDisplayActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".ui.activity.UploadFilesActivity">
+ </activity>
+ <activity android:name=".ui.activity.InstantUploadActivity">
+ </activity>
+ <activity android:name=".ui.activity.FailedUploadActivity" android:theme="@android:style/Theme.Dialog" android:excludeFromRecents="true"/>
+ <activity android:name=".Uploader" >
+ <intent-filter>
+ <action android:name="android.intent.action.SEND" >
+ </action>
+
+ <category android:name="android.intent.category.DEFAULT" >
+ </category>
+
+ <data android:mimeType="*/*" >
+ </data>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.SEND_MULTIPLE" >
+ </action>
+
+ <category android:name="android.intent.category.DEFAULT" >
+ </category>
+
+ <data android:mimeType="*/*" >
+ </data>
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".ui.activity.Preferences"
+ android:theme="@style/Theme.ownCloud" >
+ </activity>
+ <activity android:name=".ui.activity.PreferencesNewSessionewSession" >
+ </activity>
+
- <activity android:name="com.owncloud.android.ui.preview.PreviewImageActivity" />
++ <activity android:name="com.owncloud.android.ui.preview.PreviewImageActivity" />
+
+ <activity android:name="com.owncloud.android.ui.preview.PreviewVideoActivity"
+ android:label="@string/app_name"
+ android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
+ </activity>
+
+ <service
- android:name=".authenticator.AccountAuthenticatorService"
++ android:name=".authentication.AccountAuthenticatorService"
+ android:exported="true">
+ <intent-filter android:priority="100">
+ <action android:name="android.accounts.AccountAuthenticator" />
+ </intent-filter>
+
+ <meta-data
+ android:name="android.accounts.AccountAuthenticator"
+ android:resource="@xml/authenticator" />
+ </service>
+ <service
+ android:name=".syncadapter.FileSyncService"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.content.SyncAdapter" />
+ </intent-filter>
+
+ <meta-data
+ android:name="android.content.SyncAdapter"
+ android:resource="@xml/syncadapter_files" />
+ </service>
+
+ <provider
+ android:name=".providers.FileContentProvider"
+ android:authorities="org.owncloud"
+ android:enabled="true"
+ android:exported="false"
+ android:label="@string/sync_string_files"
+ android:syncable="true" >
+ </provider>
+
+ <activity
- android:name=".ui.activity.AuthenticatorActivity"
++ android:name=".authentication.AuthenticatorActivity"
+ android:exported="true"
- android:theme="@style/Theme.ownCloud.noActionBar" >
++ android:theme="@style/Theme.ownCloud.noActionBar"
++ android:launchMode="singleTask">
++ <intent-filter>
++ <action android:name="android.intent.action.VIEW" />
++ <category android:name="android.intent.category.DEFAULT" />
++ <category android:name="android.intent.category.BROWSABLE" />
++ <data android:scheme="@string/oauth2_redirect_scheme" />
++ </intent-filter>
+ <intent-filter>
+ <action android:name="com.owncloud.android.workaround.accounts.CREATE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <service android:name=".files.services.FileDownloader" />
+ <service android:name=".files.services.FileUploader" />
+ <service android:name=".media.MediaService" />
+
<activity android:name=".ui.activity.FileDetailActivity" />
- <activity android:name=".ui.activity.PinCodeActivity" />\r
+ <activity android:name=".ui.activity.PinCodeActivity" />
<activity android:name=".extensions.ExtensionsAvailableActivity"></activity>
- <activity android:name=".extensions.ExtensionsListActivity"></activity>\r
- <activity android:name=".ui.activity.AccountSelectActivity" android:uiOptions="none" android:label="@string/prefs_accounts"></activity>\r
+ <activity android:name=".extensions.ExtensionsListActivity"></activity>
+ <activity android:name=".ui.activity.AccountSelectActivity" android:uiOptions="none" android:label="@string/prefs_accounts"></activity>
<activity android:name=".ui.activity.ConflictsResolveActivity"/>
- <activity android:name=".ui.activity.GenericExplanationActivity"/>\r
- <activity android:name=".ui.activity.ErrorsWhileCopyingHandlerActivity"/>\r
+ <activity android:name=".ui.activity.GenericExplanationActivity"/>
+ <activity android:name=".ui.activity.ErrorsWhileCopyingHandlerActivity"/>
+
+ <activity android:name=".ui.activity.LogHistoryActivity"/>
- <service android:name=".files.services.FileUploader" >\r
- </service>
- <receiver android:name=".files.InstantUploadBroadcastReceiver">
- <intent-filter>
- <action android:name="com.android.camera.NEW_PICTURE" />
- <data android:mimeType="image/*" />
+ <receiver android:name=".files.InstantUploadBroadcastReceiver">\r
+ <intent-filter>\r
+ <action android:name="com.android.camera.NEW_PICTURE" />\r
+ <data android:mimeType="image/*" />\r
</intent-filter>
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
<string name="downloader_download_succeeded_content">%1$s was successfully downloaded</string>
<string name="downloader_download_failed_ticker">Download failed</string>
<string name="downloader_download_failed_content">Download of %1$s could not be completed</string>
+ <string name="downloader_not_downloaded_yet">Not downloaded yet</string>
<string name="common_choose_account">Choose account</string>
<string name="sync_string_contacts">Contacts</string>
- <string name="sync_fail_ticker">Synchronization failed</string>
+ <string name="sync_fail_ticker">Synchronization failed</string>
<string name="sync_fail_content">Synchronization of %1$s could not be completed</string>
- <string name="sync_conflicts_in_favourites_ticker">Conflicts found</string>
- <string name="sync_conflicts_in_favourites_content">%1$d kept-in-sync files could not be sync\'ed</string>
+ <string name="sync_fail_content_unauthorized">Invalid credentials for %1$s</string>
+ <string name="sync_conflicts_in_favourites_ticker">Conflicts found</string>
+ <string name="sync_conflicts_in_favourites_content">%1$d kept-in-sync files could not be sync\'ed</string>
<string name="sync_fail_in_favourites_ticker">Kept-in-sync files failed</string>
- <string name="sync_fail_in_favourites_content">Contents of %1$d files could not be sync\'ed (%2$d conflicts)</string>
- <string name="sync_foreign_files_forgotten_ticker">Some local files were forgotten</string>
- <string name="sync_foreign_files_forgotten_content">%1$d files out of the %2$s directory could not be copied into</string>
- <string name="sync_foreign_files_forgotten_explanation">"As of version 1.3.16, files uploaded from this device are copied into the local %1$s folder to prevent data loss when a single file is synced with multiple accounts.\n\nDue to this change, all files uploaded in previous versions of this app were copied into the %2$s folder. However, an error prevented the completion of this operation during account synchronization. You may either leave the file(s) as is and remove the link to %3$s, or move the file(s) into the %1$s directory and retain the link to %4$s.\n\nListed below are the local file(s), and the the remote file(s) in %5$s they were linked to.</string>
-
+ <string name="sync_fail_in_favourites_content">Contents of %1$d files could not be sync\'ed (%2$d conflicts)</string>
+ <string name="sync_foreign_files_forgotten_ticker">Some local files were forgotten</string>
+ <string name="sync_foreign_files_forgotten_content">%1$d files out of the %2$s directory could not be copied into</string>
+ <string name="sync_foreign_files_forgotten_explanation">"As of version 1.3.16, files uploaded from this device are copied into the local %1$s folder to prevent data loss when a single file is synced with multiple accounts.\n\nDue to this change, all files uploaded in previous versions of this app were copied into the %2$s folder. However, an error prevented the completion of this operation during account synchronization. You may either leave the file(s) as is and remove the link to %3$s, or move the file(s) into the %1$s directory and retain the link to %4$s.\n\nListed below are the local file(s), and the the remote file(s) in %5$s they were linked to.</string>
<string name="foreign_files_move">"Move all"</string>
<string name="foreign_files_success">"All files were moved"</string>
<string name="foreign_files_fail">"Some files could not be moved"</string>
<string name="auth_incorrect_path_message">Application couldn\'t find a server instance at the given path. Please check your path and try again.</string>
<string name="auth_timeout_title">The server took too long to respond</string>
<string name="auth_incorrect_address_title">Malformed URL</string>
- <string name="auth_ssl_general_error_title">SSL initialization failed</string>
- <string name="auth_ssl_unverified_server_title">Unverified SSL server\'s identity</string>
- <string name="auth_bad_oc_version_title">Unrecognized server version</string>
- <string name="auth_wrong_connection_title">Couldn\'t establish connection</string>
- <string name="auth_secure_connection">Secure connection established</string>
+ <string name="auth_ssl_general_error_title">SSL initialization failed</string>
+ <string name="auth_ssl_unverified_server_title">Unverified SSL server\'s identity</string>
+ <string name="auth_bad_oc_version_title">Unrecognized server version</string>
+ <string name="auth_wrong_connection_title">Couldn\'t establish connection</string>
+ <string name="auth_secure_connection">Secure connection established</string>
<string name="auth_login_details">Login details</string>
- <string name="auth_unauthorized">Invalid login / password</string>
+ <string name="auth_unauthorized">Invalid credentials</string>
+ <string name="auth_oauth_error">Unsuccessful authorization</string>
+ <string name="auth_oauth_error_access_denied">Access denied by authorization server</string>
<string name="auth_not_found">Wrong path given</string>
<string name="auth_internal">Internal server error, code %1$d</string>
+ <string name="auth_wtf_reenter_URL">Unexpected state; please, enter the server URL again</string>
+ <string name="auth_expired_oauth_token_toast">Your authorization expired.\nPlease, authorize again</string>
+ <string name="auth_expired_basic_auth_toast">Your saved credentials are invalid.\nPlease, enter the current credentials</string>
+
<string name="crashlog_message">Application terminated unexpectedly. Would you like to submit a crash report?</string>
<string name="crashlog_send_report">Send report</string>
<string name="crashlog_dont_send_report">Don\'t send report</string>
<string name="rename_dialog_title">Enter a new name</string>
<string name="rename_local_fail_msg">"Local copy could not be renamed; try a different name"</string>
<string name="rename_server_fail_msg">"Rename could not be completed"</string>
-
- <string name="sync_file_fail_msg">Remote file could not be checked</string>
- <string name="sync_file_nothing_to_do_msg">File contents already synchronized</string>
-
+ <string name="sync_file_fail_msg">Remote file could not be checked</string>
+ <string name="sync_file_nothing_to_do_msg">File contents already synchronized</string>
<string name="create_dir_fail_msg">Directory could not be created</string>
-
<string name="wait_a_moment">Wait a moment</string>
-
<string name="filedisplay_unexpected_bad_get_content">"Unexpected problem ; please select the file from a different app"</string>
<string name="filedisplay_no_file_selected">No file was selected</string>
+
+ <string name="oauth_host_url">oAuth2 URL</string>
+ <string name="oauth_check_onoff">Login with oAuth2.</string>
+ <string name="oauth_login_connection">Connecting to oAuth2 server…</string>
+ <string name="oauth_code_validation_message">Please, open a web browser and go to:\n%1$s.\nValidate this code there:\n%2$s</string>
+ <string name="oauth_connection_url_unavailable">Connection to this URL not available.</string>
+
<string name="ssl_validator_title">Warning</string>
<string name="ssl_validator_header">The identity of the site could not be verified</string>
<string name="ssl_validator_reason_cert_not_trusted">- The server certificate is not trusted</string>
import java.util.HashSet;\r
import java.util.Set;\r
\r
--import android.util.Log;\r
--\r
/**\r
* A helper class for some string operations.\r
* \r
- /* ownCloud Android client application\r
- * Copyright (C) 2012 Bartek Przybylski\r
- * Copyright (C) 2012-2013 ownCloud Inc.\r
- *\r
- * This program is free software: you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 2 of the License, or\r
- * (at your option) any later version.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program. If not, see <http://www.gnu.org/licenses/>.\r
- *\r
- */\r
- package com.owncloud.android;\r
- \r
- import java.io.File;\r
- import java.util.ArrayList;\r
- import java.util.HashMap;\r
- import java.util.LinkedList;\r
- import java.util.List;\r
- import java.util.Stack;\r
- import java.util.Vector;\r
- \r
- import com.owncloud.android.authentication.AccountAuthenticator;\r
- import com.owncloud.android.datamodel.DataStorageManager;\r
- import com.owncloud.android.datamodel.FileDataStorageManager;\r
- import com.owncloud.android.datamodel.OCFile;\r
- import com.owncloud.android.files.services.FileUploader;\r
- \r
- import android.accounts.Account;\r
- import android.accounts.AccountManager;\r
- import android.app.AlertDialog;\r
- import android.app.AlertDialog.Builder;\r
- import android.app.Dialog;\r
- import android.app.ListActivity;\r
- import android.app.ProgressDialog;\r
- import android.content.Context;\r
- import android.content.DialogInterface;\r
- import android.content.DialogInterface.OnCancelListener;\r
- import android.content.DialogInterface.OnClickListener;\r
- import android.content.Intent;\r
- import android.database.Cursor;\r
- import android.net.Uri;\r
- import android.os.Bundle;\r
- import android.os.Parcelable;\r
- import android.provider.MediaStore.Images.Media;\r
- import android.util.Log;\r
- import android.view.View;\r
- import android.view.Window;\r
- import android.widget.AdapterView;\r
- import android.widget.AdapterView.OnItemClickListener;\r
- import android.widget.Button;\r
- import android.widget.EditText;\r
- import android.widget.SimpleAdapter;\r
- import android.widget.Toast;\r
- \r
- import com.owncloud.android.R;\r
- \r
- /**\r
- * This can be used to upload things to an ownCloud instance.\r
- * \r
- * @author Bartek Przybylski\r
- * \r
- */\r
- public class Uploader extends ListActivity implements OnItemClickListener, android.view.View.OnClickListener {\r
- private static final String TAG = "ownCloudUploader";\r
- \r
- private Account mAccount;\r
- private AccountManager mAccountManager;\r
- private Stack<String> mParents;\r
- private ArrayList<Parcelable> mStreamsToUpload;\r
- private boolean mCreateDir;\r
- private String mUploadPath;\r
- private static final String[] CONTENT_PROJECTION = { Media.DATA, Media.DISPLAY_NAME, Media.MIME_TYPE, Media.SIZE };\r
- private DataStorageManager mStorageManager;\r
- private OCFile mFile;\r
- \r
- private final static int DIALOG_NO_ACCOUNT = 0;\r
- private final static int DIALOG_WAITING = 1;\r
- private final static int DIALOG_NO_STREAM = 2;\r
- private final static int DIALOG_MULTIPLE_ACCOUNT = 3;\r
- //private final static int DIALOG_GET_DIRNAME = 4;\r
- \r
- private final static int REQUEST_CODE_SETUP_ACCOUNT = 0;\r
- \r
- @Override\r
- protected void onCreate(Bundle savedInstanceState) {\r
- super.onCreate(savedInstanceState);\r
- getWindow().requestFeature(Window.FEATURE_NO_TITLE);\r
- mParents = new Stack<String>();\r
- mParents.add("");\r
- /*if (getIntent().hasExtra(Intent.EXTRA_STREAM)) {\r
- prepareStreamsToUpload();*/\r
- if (prepareStreamsToUpload()) {\r
- mAccountManager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE);\r
- Account[] accounts = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE);\r
- if (accounts.length == 0) {\r
- Log.i(TAG, "No ownCloud account is available");\r
- showDialog(DIALOG_NO_ACCOUNT);\r
- } else if (accounts.length > 1) {\r
- Log.i(TAG, "More then one ownCloud is available");\r
- showDialog(DIALOG_MULTIPLE_ACCOUNT);\r
- } else {\r
- mAccount = accounts[0];\r
- mStorageManager = new FileDataStorageManager(mAccount, getContentResolver());\r
- populateDirectoryList();\r
- }\r
- } else {\r
- showDialog(DIALOG_NO_STREAM);\r
- }\r
- }\r
- \r
- @Override\r
- protected Dialog onCreateDialog(final int id) {\r
- final AlertDialog.Builder builder = new Builder(this);\r
- switch (id) {\r
- case DIALOG_WAITING:\r
- ProgressDialog pDialog = new ProgressDialog(this);\r
- pDialog.setIndeterminate(false);\r
- pDialog.setCancelable(false);\r
- pDialog.setMessage(getResources().getString(R.string.uploader_info_uploading));\r
- return pDialog;\r
- case DIALOG_NO_ACCOUNT:\r
- builder.setIcon(android.R.drawable.ic_dialog_alert);\r
- builder.setTitle(R.string.uploader_wrn_no_account_title);\r
- builder.setMessage(String.format(getString(R.string.uploader_wrn_no_account_text), getString(R.string.app_name)));\r
- builder.setCancelable(false);\r
- builder.setPositiveButton(R.string.uploader_wrn_no_account_setup_btn_text, new OnClickListener() {\r
- @Override\r
- public void onClick(DialogInterface dialog, int which) {\r
- if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ECLAIR_MR1) {\r
- // using string value since in API7 this\r
- // constatn is not defined\r
- // in API7 < this constatant is defined in\r
- // Settings.ADD_ACCOUNT_SETTINGS\r
- // and Settings.EXTRA_AUTHORITIES\r
- Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT);\r
- intent.putExtra("authorities", new String[] { AccountAuthenticator.AUTHORITY });\r
- startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT);\r
- } else {\r
- // since in API7 there is no direct call for\r
- // account setup, so we need to\r
- // show our own AccountSetupAcricity, get\r
- // desired results and setup\r
- // everything for ourself\r
- Intent intent = new Intent(getBaseContext(), AccountAuthenticator.class);\r
- startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT);\r
- }\r
- }\r
- });\r
- builder.setNegativeButton(R.string.uploader_wrn_no_account_quit_btn_text, new OnClickListener() {\r
- @Override\r
- public void onClick(DialogInterface dialog, int which) {\r
- finish();\r
- }\r
- });\r
- return builder.create();\r
- /*case DIALOG_GET_DIRNAME:\r
- final EditText dirName = new EditText(getBaseContext());\r
- builder.setView(dirName);\r
- builder.setTitle(R.string.uploader_info_dirname);\r
- String pathToUpload;\r
- if (mParents.empty()) {\r
- pathToUpload = "/";\r
- } else {\r
- mCursor = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, mParents.peek()), null,\r
- null, null, null);\r
- mCursor.moveToFirst();\r
- pathToUpload = mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_PATH))\r
- + mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_NAME)).replace(" ", "%20"); // TODO don't make this ; use WebdavUtils.encode in the right moment\r
- }\r
- a a = new a(pathToUpload, dirName);\r
- builder.setPositiveButton(R.string.common_ok, a);\r
- builder.setNegativeButton(R.string.common_cancel, new OnClickListener() {\r
- public void onClick(DialogInterface dialog, int which) {\r
- dialog.cancel();\r
- }\r
- });\r
- return builder.create();*/\r
- case DIALOG_MULTIPLE_ACCOUNT:\r
- CharSequence ac[] = new CharSequence[mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE).length];\r
- for (int i = 0; i < ac.length; ++i) {\r
- ac[i] = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE)[i].name;\r
- }\r
- builder.setTitle(R.string.common_choose_account);\r
- builder.setItems(ac, new OnClickListener() {\r
- @Override\r
- public void onClick(DialogInterface dialog, int which) {\r
- mAccount = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE)[which];\r
- mStorageManager = new FileDataStorageManager(mAccount, getContentResolver());\r
- populateDirectoryList();\r
- }\r
- });\r
- builder.setCancelable(true);\r
- builder.setOnCancelListener(new OnCancelListener() {\r
- @Override\r
- public void onCancel(DialogInterface dialog) {\r
- dialog.cancel();\r
- finish();\r
- }\r
- });\r
- return builder.create();\r
- case DIALOG_NO_STREAM:\r
- builder.setIcon(android.R.drawable.ic_dialog_alert);\r
- builder.setTitle(R.string.uploader_wrn_no_content_title);\r
- builder.setMessage(R.string.uploader_wrn_no_content_text);\r
- builder.setCancelable(false);\r
- builder.setNegativeButton(R.string.common_cancel, new OnClickListener() {\r
- @Override\r
- public void onClick(DialogInterface dialog, int which) {\r
- finish();\r
- }\r
- });\r
- return builder.create();\r
- default:\r
- throw new IllegalArgumentException("Unknown dialog id: " + id);\r
- }\r
- }\r
- \r
- class a implements OnClickListener {\r
- String mPath;\r
- EditText mDirname;\r
- \r
- public a(String path, EditText dirname) {\r
- mPath = path; \r
- mDirname = dirname;\r
- }\r
- \r
- @Override\r
- public void onClick(DialogInterface dialog, int which) {\r
- Uploader.this.mUploadPath = mPath + mDirname.getText().toString();\r
- Uploader.this.mCreateDir = true;\r
- uploadFiles();\r
- }\r
- }\r
- \r
- @Override\r
- public void onBackPressed() {\r
- \r
- if (mParents.size() <= 1) {\r
- super.onBackPressed();\r
- return;\r
- } else {\r
- mParents.pop();\r
- populateDirectoryList();\r
- }\r
- }\r
- \r
- @Override\r
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\r
- // click on folder in the list\r
- Log.d(TAG, "on item click");\r
- Vector<OCFile> tmpfiles = mStorageManager.getDirectoryContent(mFile);\r
- if (tmpfiles.size() <= 0) return;\r
- // filter on dirtype\r
- Vector<OCFile> files = new Vector<OCFile>();\r
- for (OCFile f : tmpfiles)\r
- if (f.isDirectory())\r
- files.add(f);\r
- if (files.size() < position) {\r
- throw new IndexOutOfBoundsException("Incorrect item selected");\r
- }\r
- mParents.push(files.get(position).getFileName());\r
- populateDirectoryList();\r
- }\r
- \r
- @Override\r
- public void onClick(View v) {\r
- // click on button\r
- switch (v.getId()) {\r
- case R.id.uploader_choose_folder:\r
- mUploadPath = ""; // first element in mParents is root dir, represented by ""; init mUploadPath with "/" results in a "//" prefix\r
- for (String p : mParents)\r
- mUploadPath += p + OCFile.PATH_SEPARATOR;\r
- Log.d(TAG, "Uploading file to dir " + mUploadPath);\r
- \r
- uploadFiles();\r
- \r
- break;\r
- /*case android.R.id.button1: // dynamic action for create aditional dir\r
- showDialog(DIALOG_GET_DIRNAME);\r
- break;*/\r
- default:\r
- throw new IllegalArgumentException("Wrong element clicked");\r
- }\r
- }\r
- \r
- @Override\r
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {\r
- super.onActivityResult(requestCode, resultCode, data);\r
- Log.i(TAG, "result received. req: " + requestCode + " res: " + resultCode);\r
- if (requestCode == REQUEST_CODE_SETUP_ACCOUNT) {\r
- dismissDialog(DIALOG_NO_ACCOUNT);\r
- if (resultCode == RESULT_CANCELED) {\r
- finish();\r
- }\r
- Account[] accounts = mAccountManager.getAccountsByType(AccountAuthenticator.AUTH_TOKEN_TYPE);\r
- if (accounts.length == 0) {\r
- showDialog(DIALOG_NO_ACCOUNT);\r
- } else {\r
- // there is no need for checking for is there more then one\r
- // account at this point\r
- // since account setup can set only one account at time\r
- mAccount = accounts[0];\r
- populateDirectoryList();\r
- }\r
- }\r
- }\r
- \r
- private void populateDirectoryList() {\r
- setContentView(R.layout.uploader_layout);\r
- \r
- String full_path = "";\r
- for (String a : mParents)\r
- full_path += a + "/";\r
- \r
- Log.d(TAG, "Populating view with content of : " + full_path);\r
- \r
- mFile = mStorageManager.getFileByPath(full_path);\r
- if (mFile != null) {\r
- Vector<OCFile> files = mStorageManager.getDirectoryContent(mFile);\r
- List<HashMap<String, Object>> data = new LinkedList<HashMap<String,Object>>();\r
- for (OCFile f : files) {\r
- HashMap<String, Object> h = new HashMap<String, Object>();\r
- if (f.isDirectory()) {\r
- h.put("dirname", f.getFileName());\r
- data.add(h);\r
- }\r
- }\r
- SimpleAdapter sa = new SimpleAdapter(this,\r
- data,\r
- R.layout.uploader_list_item_layout,\r
- new String[] {"dirname"},\r
- new int[] {R.id.textView1});\r
- setListAdapter(sa);\r
- Button btn = (Button) findViewById(R.id.uploader_choose_folder);\r
- btn.setOnClickListener(this);\r
- getListView().setOnItemClickListener(this);\r
- }\r
- }\r
- \r
- private boolean prepareStreamsToUpload() {\r
- if (getIntent().getAction().equals(Intent.ACTION_SEND)) {\r
- mStreamsToUpload = new ArrayList<Parcelable>();\r
- mStreamsToUpload.add(getIntent().getParcelableExtra(Intent.EXTRA_STREAM));\r
- } else if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {\r
- mStreamsToUpload = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM);\r
- }\r
- return (mStreamsToUpload != null && mStreamsToUpload.get(0) != null);\r
- }\r
- \r
- public void uploadFiles() {\r
- try {\r
- /* TODO - mCreateDir can never be true at this moment; we will replace wdc.createDirectory by CreateFolderOperation when that is fixed \r
- WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext());\r
- // create last directory in path if necessary\r
- if (mCreateDir) {\r
- wdc.createDirectory(mUploadPath);\r
- }\r
- */\r
- \r
- String[] local = new String[mStreamsToUpload.size()], remote = new String[mStreamsToUpload.size()];\r
- \r
- for (int i = 0; i < mStreamsToUpload.size(); ++i) {\r
- Uri uri = (Uri) mStreamsToUpload.get(i);\r
- if (uri.getScheme().equals("content")) {\r
- Cursor c = getContentResolver().query((Uri) mStreamsToUpload.get(i),\r
- CONTENT_PROJECTION,\r
- null,\r
- null,\r
- null);\r
- \r
- if (!c.moveToFirst())\r
- continue;\r
- \r
- final String display_name = c.getString(c.getColumnIndex(Media.DISPLAY_NAME)),\r
- data = c.getString(c.getColumnIndex(Media.DATA));\r
- local[i] = data;\r
- remote[i] = mUploadPath + display_name;\r
- } else if (uri.getScheme().equals("file")) {\r
- final File file = new File(Uri.decode(uri.toString()).replace(uri.getScheme() + "://", ""));\r
- local[i] = file.getAbsolutePath();\r
- remote[i] = mUploadPath + file.getName();\r
- }\r
- \r
- }\r
- Intent intent = new Intent(getApplicationContext(), FileUploader.class);\r
- intent.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES);\r
- intent.putExtra(FileUploader.KEY_LOCAL_FILE, local);\r
- intent.putExtra(FileUploader.KEY_REMOTE_FILE, remote);\r
- intent.putExtra(FileUploader.KEY_ACCOUNT, mAccount);\r
- startService(intent);\r
- finish();\r
- \r
- } catch (SecurityException e) {\r
- String message = String.format(getString(R.string.uploader_error_forbidden_content), getString(R.string.app_name));\r
- Toast.makeText(this, message, Toast.LENGTH_LONG).show(); \r
- }\r
- }\r
- \r
- }\r
+ /* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
++
+ 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.authenticator.AccountAuthenticator;
++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.network.OwnCloudClientUtils;
+
+ 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.Images.Media;
-import android.util.Log;
+ import android.view.View;
+ import android.view.Window;
+ import android.widget.AdapterView;
+ import android.widget.AdapterView.OnItemClickListener;
+ import android.widget.Button;
+ import android.widget.EditText;
+ import android.widget.SimpleAdapter;
+ import android.widget.Toast;
+
+ import com.owncloud.android.R;
-import eu.alefzero.webdav.WebdavClient;
+
+ /**
+ * 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<String> mParents;
+ private ArrayList<Parcelable> mStreamsToUpload;
+ private boolean mCreateDir;
+ private String mUploadPath;
+ private static final String[] CONTENT_PROJECTION = { Media.DATA, Media.DISPLAY_NAME, Media.MIME_TYPE, Media.SIZE };
+ 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 DIALOG_GET_DIRNAME = 4;
+
+ 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<String>();
+ mParents.add("");
+ /*if (getIntent().hasExtra(Intent.EXTRA_STREAM)) {
+ prepareStreamsToUpload();*/
+ if (prepareStreamsToUpload()) {
+ mAccountManager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE);
+ Account[] accounts = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE);
+ 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.settings.ADD_ACCOUNT_SETTINGS");
- intent.putExtra("authorities", new String[] { AccountAuthenticator.AUTH_TOKEN_TYPE });
++ Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT);
++ intent.putExtra("authorities", new String[] { AccountAuthenticator.AUTHORITY });
+ 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_GET_DIRNAME:
+ final EditText dirName = new EditText(getBaseContext());
+ builder.setView(dirName);
+ builder.setTitle(R.string.uploader_info_dirname);
+ String pathToUpload;
+ if (mParents.empty()) {
+ pathToUpload = "/";
+ } else {
+ mCursor = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, mParents.peek()), null,
+ null, null, null);
+ mCursor.moveToFirst();
+ pathToUpload = mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_PATH))
+ + mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_NAME)).replace(" ", "%20"); // TODO don't make this ; use WebdavUtils.encode in the right moment
+ }
+ a a = new a(pathToUpload, dirName);
+ builder.setPositiveButton(R.string.common_ok, a);
+ builder.setNegativeButton(R.string.common_cancel, new OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
+ return builder.create();*/
+ case DIALOG_MULTIPLE_ACCOUNT:
+ CharSequence ac[] = new CharSequence[mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE).length];
+ for (int i = 0; i < ac.length; ++i) {
+ ac[i] = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE)[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(AccountAuthenticator.ACCOUNT_TYPE)[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<OCFile> tmpfiles = mStorageManager.getDirectoryContent(mFile);
+ if (tmpfiles.size() <= 0) return;
+ // filter on dirtype
+ Vector<OCFile> files = new Vector<OCFile>();
+ 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;
+ /*case android.R.id.button1: // dynamic action for create aditional dir
+ showDialog(DIALOG_GET_DIRNAME);
+ 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(AccountAuthenticator.AUTH_TOKEN_TYPE);
+ 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<OCFile> files = mStorageManager.getDirectoryContent(mFile);
+ List<HashMap<String, Object>> data = new LinkedList<HashMap<String,Object>>();
+ for (OCFile f : files) {
+ HashMap<String, Object> h = new HashMap<String, Object>();
+ 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);
+ Button btn = (Button) 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<Parcelable>();
+ 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 {
++ /* 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);
+ }
++ */
+
+ String[] local = new String[mStreamsToUpload.size()], remote = new String[mStreamsToUpload.size()];
+
+ for (int i = 0; i < mStreamsToUpload.size(); ++i) {
+ Uri uri = (Uri) mStreamsToUpload.get(i);
+ if (uri.getScheme().equals("content")) {
+ Cursor c = getContentResolver().query((Uri) mStreamsToUpload.get(i),
+ CONTENT_PROJECTION,
+ null,
+ null,
+ null);
+
+ if (!c.moveToFirst())
+ continue;
+
+ final String display_name = c.getString(c.getColumnIndex(Media.DISPLAY_NAME)),
+ data = c.getString(c.getColumnIndex(Media.DATA));
+ local[i] = data;
+ remote[i] = mUploadPath + display_name;
+ } else if (uri.getScheme().equals("file")) {
+ final File file = new File(Uri.decode(uri.toString()).replace(uri.getScheme() + "://", ""));
+ local[i] = file.getAbsolutePath();
+ remote[i] = mUploadPath + file.getName();
+ }
+
+ }
+ Intent intent = new Intent(getApplicationContext(), FileUploader.class);
+ intent.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES);
+ intent.putExtra(FileUploader.KEY_LOCAL_FILE, local);
+ intent.putExtra(FileUploader.KEY_REMOTE_FILE, remote);
+ 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();
+ }
+ }
+
+ }
--- /dev/null
- /* ownCloud Android client application\r
- * Copyright (C) 2012 Bartek Przybylski\r
- * Copyright (C) 2012-2013 ownCloud Inc.\r
- *\r
- * This program is free software: you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 2 of the License, or\r
- * (at your option) any later version.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program. If not, see <http://www.gnu.org/licenses/>.\r
- *\r
- */\r
- \r
- package com.owncloud.android.authentication;\r
- \r
- \r
- import android.accounts.*;\r
- import android.content.Context;\r
- import android.content.Intent;\r
- import android.os.Bundle;\r
- import android.util.Log;\r
- \r
- \r
- /**\r
- * Authenticator for ownCloud accounts.\r
- * \r
- * Controller class accessed from the system AccountManager, providing integration of ownCloud accounts with the Android system.\r
- * \r
- * TODO - better separation in operations for OAuth-capable and regular ownCloud accounts.\r
- * TODO - review completeness \r
- * \r
- * @author David A. Velasco\r
- */\r
- public class AccountAuthenticator extends AbstractAccountAuthenticator {\r
- /**\r
- * Is used by android system to assign accounts to authenticators. Should be\r
- * used by application and all extensions.\r
- */\r
- public static final String ACCOUNT_TYPE = "owncloud";\r
- public static final String AUTHORITY = "org.owncloud";\r
- public static final String AUTH_TOKEN_TYPE = "org.owncloud";\r
- public static final String AUTH_TOKEN_TYPE_PASSWORD = "owncloud.password";\r
- public static final String AUTH_TOKEN_TYPE_ACCESS_TOKEN = "owncloud.oauth2.access_token";\r
- public static final String AUTH_TOKEN_TYPE_REFRESH_TOKEN = "owncloud.oauth2.refresh_token";\r
- \r
- public static final String KEY_AUTH_TOKEN_TYPE = "authTokenType";\r
- public static final String KEY_REQUIRED_FEATURES = "requiredFeatures";\r
- public static final String KEY_LOGIN_OPTIONS = "loginOptions";\r
- public static final String KEY_ACCOUNT = "account";\r
- /**\r
- * Value under this key should handle path to webdav php script. Will be\r
- * removed and usage should be replaced by combining\r
- * {@link com.owncloud.android.authentication.AuthenticatorActivity.KEY_OC_BASE_URL} and\r
- * {@link com.owncloud.android.utils.OwnCloudVersion}\r
- * \r
- * @deprecated\r
- */\r
- public static final String KEY_OC_URL = "oc_url";\r
- /**\r
- * Version should be 3 numbers separated by dot so it can be parsed by\r
- * {@link com.owncloud.android.utils.OwnCloudVersion}\r
- */\r
- public static final String KEY_OC_VERSION = "oc_version";\r
- /**\r
- * Base url should point to owncloud installation without trailing / ie:\r
- * http://server/path or https://owncloud.server\r
- */\r
- public static final String KEY_OC_BASE_URL = "oc_base_url";\r
- /**\r
- * Flag signaling if the ownCloud server can be accessed with OAuth2 access tokens.\r
- */\r
- public static final String KEY_SUPPORTS_OAUTH2 = "oc_supports_oauth2";\r
- \r
- private static final String TAG = AccountAuthenticator.class.getSimpleName();\r
- \r
- private Context mContext;\r
- \r
- public AccountAuthenticator(Context context) {\r
- super(context);\r
- mContext = context;\r
- }\r
- \r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public Bundle addAccount(AccountAuthenticatorResponse response,\r
- String accountType, String authTokenType,\r
- String[] requiredFeatures, Bundle options)\r
- throws NetworkErrorException {\r
- Log.i(TAG, "Adding account with type " + accountType\r
- + " and auth token " + authTokenType);\r
- try {\r
- validateAccountType(accountType);\r
- } catch (AuthenticatorException e) {\r
- Log.e(TAG, "Failed to validate account type " + accountType + ": "\r
- + e.getMessage());\r
- e.printStackTrace();\r
- return e.getFailureBundle();\r
- }\r
- final Intent intent = new Intent(mContext, AuthenticatorActivity.class);\r
- intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);\r
- intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType);\r
- intent.putExtra(KEY_REQUIRED_FEATURES, requiredFeatures);\r
- intent.putExtra(KEY_LOGIN_OPTIONS, options);\r
- intent.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_CREATE);\r
- \r
- setIntentFlags(intent);\r
- \r
- final Bundle bundle = new Bundle();\r
- bundle.putParcelable(AccountManager.KEY_INTENT, intent);\r
- return bundle;\r
- }\r
- \r
++/* 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 <http://www.gnu.org/licenses/>.
++ *
++ */
++
++package com.owncloud.android.authentication;
++
++import android.accounts.*;
++import android.content.Context;
++import android.content.Intent;
++import android.os.Bundle;
++import com.owncloud.android.Log_OC;
++
++/**
++ * 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.
++ */
++ 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 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";
++
++ private static final String TAG = AccountAuthenticator.class.getSimpleName();
++
++ private Context mContext;
++
++ public AccountAuthenticator(Context context) {
++ super(context);
++ mContext = context;
++ }
++
++ /**
++ * {@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);
++ 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);
++
++ final Bundle bundle = new Bundle();
++ bundle.putParcelable(AccountManager.KEY_INTENT, intent);
++ return bundle;
++ }
++
+ /**\r
+ * {@inheritDoc}\r
+ */\r
+ @Override\r
+ public Bundle confirmCredentials(AccountAuthenticatorResponse response,\r
+ Account account, Bundle options) throws NetworkErrorException {\r
+ try {\r
+ validateAccountType(account.type);\r
+ } catch (AuthenticatorException e) {\r
- Log.e(TAG, "Failed to validate account type " + account.type + ": "\r
++ Log_OC.e(TAG, "Failed to validate account type " + account.type + ": "\r
+ + e.getMessage());\r
+ e.printStackTrace();\r
+ return e.getFailureBundle();\r
+ }\r
+ Intent intent = new Intent(mContext, AuthenticatorActivity.class);\r
+ intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,\r
+ response);\r
+ intent.putExtra(KEY_ACCOUNT, account);\r
+ intent.putExtra(KEY_LOGIN_OPTIONS, options);\r
+\r
+ setIntentFlags(intent);\r
+\r
+ Bundle resultBundle = new Bundle();\r
+ resultBundle.putParcelable(AccountManager.KEY_INTENT, intent);\r
+ return resultBundle;\r
+ }\r
+\r
+ @Override\r
+ public Bundle editProperties(AccountAuthenticatorResponse response,\r
+ String accountType) {\r
+ return null;\r
+ }\r
+\r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public Bundle getAuthToken(AccountAuthenticatorResponse response,\r
- Account account, String authTokenType, Bundle options)\r
- throws NetworkErrorException {\r
- /// validate parameters\r
- try {\r
- validateAccountType(account.type);\r
- validateAuthTokenType(authTokenType);\r
- } catch (AuthenticatorException e) {\r
- Log.e(TAG, "Failed to validate account type " + account.type + ": "\r
- + e.getMessage());\r
- e.printStackTrace();\r
- return e.getFailureBundle();\r
- }\r
- \r
- /// check if required token is stored\r
- final AccountManager am = AccountManager.get(mContext);\r
- String accessToken;\r
- if (authTokenType.equals(AUTH_TOKEN_TYPE_PASSWORD)) {\r
- accessToken = am.getPassword(account);\r
- } else {\r
- accessToken = am.peekAuthToken(account, authTokenType);\r
- }\r
- if (accessToken != null) {\r
- final Bundle result = new Bundle();\r
- result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);\r
- result.putString(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);\r
- result.putString(AccountManager.KEY_AUTHTOKEN, accessToken);\r
- return result;\r
- }\r
- \r
- /// if not stored, return Intent to access the AuthenticatorActivity and UPDATE the token for the account\r
- final Intent intent = new Intent(mContext, AuthenticatorActivity.class);\r
- intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);\r
- intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType);\r
- intent.putExtra(KEY_LOGIN_OPTIONS, options);\r
- intent.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, account);\r
- intent.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_TOKEN);\r
- \r
- \r
- final Bundle bundle = new Bundle();\r
- bundle.putParcelable(AccountManager.KEY_INTENT, intent);\r
- return bundle;\r
- }\r
- \r
- @Override\r
- public String getAuthTokenLabel(String authTokenType) {\r
- return null;\r
- }\r
- \r
- @Override\r
- public Bundle hasFeatures(AccountAuthenticatorResponse response,\r
- Account account, String[] features) throws NetworkErrorException {\r
- final Bundle result = new Bundle();\r
- result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);\r
- return result;\r
- }\r
- \r
- @Override\r
- public Bundle updateCredentials(AccountAuthenticatorResponse response,\r
- Account account, String authTokenType, Bundle options)\r
- throws NetworkErrorException {\r
- final Intent intent = new Intent(mContext, AuthenticatorActivity.class);\r
- intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,\r
- response);\r
- intent.putExtra(KEY_ACCOUNT, account);\r
- intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType);\r
- intent.putExtra(KEY_LOGIN_OPTIONS, options);\r
- setIntentFlags(intent);\r
- \r
- final Bundle bundle = new Bundle();\r
- bundle.putParcelable(AccountManager.KEY_INTENT, intent);\r
- return bundle;\r
- }\r
- \r
- @Override\r
- public Bundle getAccountRemovalAllowed(\r
- AccountAuthenticatorResponse response, Account account)\r
- throws NetworkErrorException {\r
- return super.getAccountRemovalAllowed(response, account);\r
- }\r
- \r
- private void setIntentFlags(Intent intent) {\r
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\r
- //intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);\r
- //intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); // incompatible with the authorization code grant in OAuth\r
- intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);\r
- intent.addFlags(Intent.FLAG_FROM_BACKGROUND);\r
- }\r
- \r
- private void validateAccountType(String type)\r
- throws UnsupportedAccountTypeException {\r
- if (!type.equals(ACCOUNT_TYPE)) {\r
- throw new UnsupportedAccountTypeException();\r
- }\r
- }\r
- \r
++ /**
++ * {@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(AUTH_TOKEN_TYPE_PASSWORD)) {
++ 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, ACCOUNT_TYPE);
++ 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_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(ACCOUNT_TYPE)) {
++ throw new UnsupportedAccountTypeException();
++ }
++ }
++
+ private void validateAuthTokenType(String authTokenType)\r
+ throws UnsupportedAuthTokenTypeException {\r
+ if (!authTokenType.equals(AUTH_TOKEN_TYPE) &&\r
+ !authTokenType.equals(AUTH_TOKEN_TYPE_PASSWORD) &&\r
+ !authTokenType.equals(AUTH_TOKEN_TYPE_ACCESS_TOKEN) &&\r
+ !authTokenType.equals(AUTH_TOKEN_TYPE_REFRESH_TOKEN) ) {\r
+ throw new UnsupportedAuthTokenTypeException();\r
+ }\r
+ }\r
+\r
+ public static class AuthenticatorException extends Exception {\r
+ private static final long serialVersionUID = 1L;\r
+ private Bundle mFailureBundle;\r
+\r
+ public AuthenticatorException(int code, String errorMsg) {\r
+ mFailureBundle = new Bundle();\r
+ mFailureBundle.putInt(AccountManager.KEY_ERROR_CODE, code);\r
+ mFailureBundle\r
+ .putString(AccountManager.KEY_ERROR_MESSAGE, errorMsg);\r
+ }\r
+\r
+ public Bundle getFailureBundle() {\r
+ return mFailureBundle;\r
+ }\r
+ }\r
+\r
+ public static class UnsupportedAccountTypeException extends\r
+ AuthenticatorException {\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ public UnsupportedAccountTypeException() {\r
+ super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,\r
+ "Unsupported account type");\r
+ }\r
+ }\r
+\r
+ public static class UnsupportedAuthTokenTypeException extends\r
+ AuthenticatorException {\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ public UnsupportedAuthTokenTypeException() {\r
+ super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,\r
+ "Unsupported auth token type");\r
+ }\r
+ }\r
+\r
+ public static class UnsupportedFeaturesException extends\r
+ AuthenticatorException {\r
+ public static final long serialVersionUID = 1L;\r
+\r
+ public UnsupportedFeaturesException() {\r
+ super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,\r
+ "Unsupported features");\r
+ }\r
+ }\r
+\r
+ public static class AccessDeniedException extends AuthenticatorException {\r
+ public AccessDeniedException(int code, String errorMsg) {\r
+ super(AccountManager.ERROR_CODE_INVALID_RESPONSE, "Access Denied");\r
+ }\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ }\r
+}\r
--- /dev/null
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+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();
+ }
+
+}
--- /dev/null
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 2 of the License, or\r
- * (at your option) any later version.\r
+/* ownCloud Android client application\r
+ * Copyright (C) 2012 Bartek Przybylski\r
+ * Copyright (C) 2012-2013 ownCloud Inc.\r
+ *\r
+ * This program is free software: you can redistribute it and/or modify\r
- import android.util.Log;\r
++ * it under the terms of the GNU General Public License version 2,\r
++ * as published by the Free Software Foundation.\r
+ *\r
+ * This program is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+ * GNU General Public License for more details.\r
+ *\r
+ * You should have received a copy of the GNU General Public License\r
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.\r
+ *\r
+ */\r
+\r
+package com.owncloud.android.authentication;\r
+\r
+import com.owncloud.android.AccountUtils;\r
++import com.owncloud.android.Log_OC;\r
+import com.owncloud.android.ui.dialog.SslValidatorDialog;\r
+import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener;\r
+import com.owncloud.android.utils.OwnCloudVersion;\r
+import com.owncloud.android.network.OwnCloudClientUtils;\r
+import com.owncloud.android.operations.OwnCloudServerCheckOperation;\r
+import com.owncloud.android.operations.ExistenceCheckOperation;\r
+import com.owncloud.android.operations.OAuth2GetAccessToken;\r
+import com.owncloud.android.operations.OnRemoteOperationListener;\r
+import com.owncloud.android.operations.RemoteOperation;\r
+import com.owncloud.android.operations.RemoteOperationResult;\r
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;\r
+\r
+import android.accounts.Account;\r
+import android.accounts.AccountAuthenticatorActivity;\r
+import android.accounts.AccountManager;\r
+import android.app.AlertDialog;\r
+import android.app.Dialog;\r
+import android.app.ProgressDialog;\r
+import android.content.ContentResolver;\r
+import android.content.DialogInterface;\r
+import android.content.Intent;\r
+import android.content.SharedPreferences;\r
+import android.net.Uri;\r
+import android.os.Bundle;\r
+import android.os.Handler;\r
+import android.preference.PreferenceManager;\r
+import android.text.InputType;\r
- Log.d(TAG, "onNewIntent()");\r
+import android.view.View;\r
+import android.view.View.OnFocusChangeListener;\r
+import android.view.Window;\r
+import android.widget.CheckBox;\r
+import android.widget.EditText;\r
+import android.widget.Button;\r
+import android.widget.ImageView;\r
+import android.widget.TextView;\r
+import android.widget.Toast;\r
+\r
+import com.owncloud.android.R;\r
+\r
+import eu.alefzero.webdav.WebdavClient;\r
+\r
+/**\r
+ * This Activity is used to add an ownCloud account to the App\r
+ * \r
+ * @author Bartek Przybylski\r
+ * @author David A. Velasco\r
+ */\r
+public class AuthenticatorActivity extends AccountAuthenticatorActivity\r
+ implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeListener {\r
+\r
+ private static final String TAG = AuthenticatorActivity.class.getSimpleName();\r
+\r
+ public static final String EXTRA_ACCOUNT = "ACCOUNT";\r
+ public static final String EXTRA_USER_NAME = "USER_NAME";\r
+ public static final String EXTRA_HOST_NAME = "HOST_NAME";\r
+ public static final String EXTRA_ACTION = "ACTION";\r
+ \r
+ private static final String KEY_HOST_URL_TEXT = "HOST_URL_TEXT";\r
+ private static final String KEY_OC_VERSION = "OC_VERSION";\r
+ private static final String KEY_ACCOUNT = "ACCOUNT";\r
+ private static final String KEY_STATUS_TEXT = "STATUS_TEXT";\r
+ private static final String KEY_STATUS_ICON = "STATUS_ICON";\r
+ private static final String KEY_STATUS_CORRECT = "STATUS_CORRECT";\r
+ private static final String KEY_IS_SSL_CONN = "IS_SSL_CONN";\r
+ private static final String KEY_OAUTH2_STATUS_TEXT = "OAUTH2_STATUS_TEXT";\r
+ private static final String KEY_OAUTH2_STATUS_ICON = "OAUTH2_STATUS_ICON";\r
+ \r
+ private static final String OAUTH_MODE_ON = "on";\r
+ private static final String OAUTH_MODE_OFF = "off";\r
+ private static final String OAUTH_MODE_OPTIONAL = "optional";\r
+\r
+ private static final int DIALOG_LOGIN_PROGRESS = 0;\r
+ private static final int DIALOG_SSL_VALIDATOR = 1;\r
+ private static final int DIALOG_CERT_NOT_SAVED = 2;\r
+ private static final int DIALOG_OAUTH2_LOGIN_PROGRESS = 3;\r
+\r
+ public static final byte ACTION_CREATE = 0;\r
+ public static final byte ACTION_UPDATE_TOKEN = 1;\r
+\r
+ \r
+ private String mHostBaseUrl;\r
+ private OwnCloudVersion mDiscoveredVersion;\r
+ \r
+ private int mStatusText, mStatusIcon;\r
+ private boolean mStatusCorrect, mIsSslConn;\r
+ private int mOAuth2StatusText, mOAuth2StatusIcon; \r
+ \r
+ private final Handler mHandler = new Handler();\r
+ private Thread mOperationThread;\r
+ private OwnCloudServerCheckOperation mOcServerChkOperation;\r
+ private ExistenceCheckOperation mAuthCheckOperation;\r
+ private RemoteOperationResult mLastSslUntrustedServerResult;\r
+\r
+ private Uri mNewCapturedUriFromOAuth2Redirection;\r
+ \r
+ private AccountManager mAccountMgr;\r
+ private boolean mJustCreated;\r
+ private byte mAction;\r
+ private Account mAccount;\r
+ \r
+ private ImageView mRefreshButton;\r
+ private ImageView mViewPasswordButton;\r
+ private EditText mHostUrlInput;\r
+ private EditText mUsernameInput;\r
+ private EditText mPasswordInput;\r
+ private CheckBox mOAuth2Check;\r
+ private String mOAuthAccessToken;\r
+ private View mOkButton;\r
+ private TextView mAuthStatusLayout;\r
+ \r
+ private TextView mOAuthAuthEndpointText;\r
+ private TextView mOAuthTokenEndpointText;\r
+ \r
+ \r
+ /**\r
+ * {@inheritDoc}\r
+ * \r
+ * IMPORTANT ENTRY POINT 1: activity is shown to the user\r
+ */\r
+ @Override\r
+ protected void onCreate(Bundle savedInstanceState) {\r
+ super.onCreate(savedInstanceState);\r
+ getWindow().requestFeature(Window.FEATURE_NO_TITLE);\r
+ \r
+ /// set view and get references to view elements\r
+ setContentView(R.layout.account_setup);\r
+ mRefreshButton = (ImageView) findViewById(R.id.refreshButton);\r
+ mViewPasswordButton = (ImageView) findViewById(R.id.viewPasswordButton);\r
+ mHostUrlInput = (EditText) findViewById(R.id.hostUrlInput);\r
+ mUsernameInput = (EditText) findViewById(R.id.account_username);\r
+ mPasswordInput = (EditText) findViewById(R.id.account_password);\r
+ mOAuthAuthEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_1);\r
+ mOAuthTokenEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_2);\r
+ mOAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check);\r
+ mOkButton = findViewById(R.id.buttonOK);\r
+ mAuthStatusLayout = (TextView) findViewById(R.id.auth_status_text); \r
+ \r
+ /// complete label for 'register account' button\r
+ Button b = (Button) findViewById(R.id.account_register);\r
+ if (b != null) {\r
+ b.setText(String.format(getString(R.string.auth_register), getString(R.string.app_name)));\r
+ }\r
+\r
+ /// bind view elements to listeners\r
+ mHostUrlInput.setOnFocusChangeListener(this);\r
+ mPasswordInput.setOnFocusChangeListener(this);\r
+ \r
+ /// initialization\r
+ mAccountMgr = AccountManager.get(this);\r
+ mNewCapturedUriFromOAuth2Redirection = null;\r
+ mAction = getIntent().getByteExtra(EXTRA_ACTION, ACTION_CREATE); \r
+ mAccount = null;\r
+\r
+ if (savedInstanceState == null) {\r
+ /// connection state and info\r
+ mStatusText = mStatusIcon = 0;\r
+ mStatusCorrect = false;\r
+ mIsSslConn = false;\r
+ \r
+ /// retrieve extras from intent\r
+ String tokenType = getIntent().getExtras().getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE);\r
+ boolean oAuthRequired = AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN.equals(tokenType) || OAUTH_MODE_ON.equals(getString(R.string.oauth2_mode));\r
+ \r
+ mAccount = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT);\r
+ if (mAccount != null) {\r
+ String ocVersion = mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION);\r
+ if (ocVersion != null) {\r
+ mDiscoveredVersion = new OwnCloudVersion(ocVersion);\r
+ }\r
+ mHostBaseUrl = mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL);\r
+ mHostUrlInput.setText(mHostBaseUrl);\r
+ String userName = mAccount.name.substring(0, mAccount.name.lastIndexOf('@'));\r
+ mUsernameInput.setText(userName);\r
+ oAuthRequired = (mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null);\r
+ }\r
+ mOAuth2Check.setChecked(oAuthRequired);\r
+ changeViewByOAuth2Check(oAuthRequired);\r
+ \r
+\r
+ } else {\r
+ loadSavedInstanceState(savedInstanceState);\r
+ }\r
+ \r
+ if (!OAUTH_MODE_OPTIONAL.equals(getString(R.string.oauth2_mode))) {\r
+ mOAuth2Check.setVisibility(View.GONE);\r
+ }\r
+ \r
+ if (mAction == ACTION_UPDATE_TOKEN) {\r
+ /// lock things that should not change\r
+ mHostUrlInput.setEnabled(false);\r
+ mUsernameInput.setEnabled(false);\r
+ mOAuth2Check.setVisibility(View.GONE);\r
+ checkOcServer(); \r
+ }\r
+ \r
+ mPasswordInput.setText(""); // clean password to avoid social hacking (disadvantage: password in removed if the device is turned aside)\r
+ mJustCreated = true;\r
+ }\r
+\r
+\r
+ /**\r
+ * Saves relevant state before {@link #onPause()}\r
+ * \r
+ * Do NOT save {@link #mNewCapturedUriFromOAuth2Redirection}; it keeps a temporal flag, intended to defer the \r
+ * processing of the redirection caught in {@link #onNewIntent(Intent)} until {@link #onResume()} \r
+ * \r
+ * See {@link #loadSavedInstanceState(Bundle)}\r
+ */\r
+ @Override\r
+ protected void onSaveInstanceState(Bundle outState) {\r
+ super.onSaveInstanceState(outState);\r
+ \r
+ /// connection state and info\r
+ outState.putInt(KEY_STATUS_TEXT, mStatusText);\r
+ outState.putInt(KEY_STATUS_ICON, mStatusIcon);\r
+ outState.putBoolean(KEY_STATUS_CORRECT, mStatusCorrect);\r
+ outState.putBoolean(KEY_IS_SSL_CONN, mIsSslConn);\r
+\r
+ /// server data\r
+ if (mDiscoveredVersion != null) \r
+ outState.putString(KEY_OC_VERSION, mDiscoveredVersion.toString());\r
+ outState.putString(KEY_HOST_URL_TEXT, mHostBaseUrl);\r
+ \r
+ /// account data, if updating\r
+ if (mAccount != null)\r
+ outState.putParcelable(KEY_ACCOUNT, mAccount);\r
+ \r
+ // Saving the state of oAuth2 components.\r
+ outState.putInt(KEY_OAUTH2_STATUS_ICON, mOAuth2StatusIcon);\r
+ outState.putInt(KEY_OAUTH2_STATUS_TEXT, mOAuth2StatusText);\r
+ \r
+ }\r
+\r
+\r
+ /**\r
+ * Loads saved state\r
+ * \r
+ * See {@link #onSaveInstanceState(Bundle)}.\r
+ * \r
+ * @param savedInstanceState Saved state, as received in {@link #onCreate(Bundle)}.\r
+ */\r
+ private void loadSavedInstanceState(Bundle savedInstanceState) {\r
+ /// connection state and info\r
+ mStatusCorrect = savedInstanceState.getBoolean(KEY_STATUS_CORRECT);\r
+ mIsSslConn = savedInstanceState.getBoolean(KEY_IS_SSL_CONN);\r
+ mStatusText = savedInstanceState.getInt(KEY_STATUS_TEXT);\r
+ mStatusIcon = savedInstanceState.getInt(KEY_STATUS_ICON);\r
+ updateConnStatus();\r
+ \r
+ /// UI settings depending upon connection\r
+ mOkButton.setEnabled(mStatusCorrect); // TODO really necessary?\r
+ if (!mStatusCorrect)\r
+ mRefreshButton.setVisibility(View.VISIBLE); // seems that setting visibility is necessary\r
+ else\r
+ mRefreshButton.setVisibility(View.INVISIBLE);\r
+ \r
+ /// server data\r
+ String ocVersion = savedInstanceState.getString(KEY_OC_VERSION);\r
+ if (ocVersion != null)\r
+ mDiscoveredVersion = new OwnCloudVersion(ocVersion);\r
+ mHostBaseUrl = savedInstanceState.getString(KEY_HOST_URL_TEXT);\r
+ \r
+ // account data, if updating\r
+ mAccount = savedInstanceState.getParcelable(KEY_ACCOUNT);\r
+ \r
+ // state of oAuth2 components\r
+ mOAuth2StatusIcon = savedInstanceState.getInt(KEY_OAUTH2_STATUS_ICON);\r
+ mOAuth2StatusText = savedInstanceState.getInt(KEY_OAUTH2_STATUS_TEXT);\r
+ // END of getting the state of oAuth2 components.\r
+ }\r
+\r
+ \r
+ /**\r
+ * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION request\r
+ * is caught here.\r
+ * \r
+ * To make this possible, this activity needs to be qualified with android:launchMode = "singleTask" in the\r
+ * AndroidManifest.xml file.\r
+ */\r
+ @Override\r
+ protected void onNewIntent (Intent intent) {\r
- Log.wtf(TAG, "The user was allowed to click 'connect' to an unchecked server!!");\r
++ Log_OC.d(TAG, "onNewIntent()");\r
+ Uri data = intent.getData();\r
+ if (data != null && data.toString().startsWith(getString(R.string.oauth2_redirect_uri))) {\r
+ mNewCapturedUriFromOAuth2Redirection = data;\r
+ }\r
+ }\r
+\r
+ \r
+ /**\r
+ * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION, and \r
+ * deferred in {@link #onNewIntent(Intent)}, is processed here.\r
+ */\r
+ @Override\r
+ protected void onResume() {\r
+ super.onResume();\r
+ // the state of mOAuth2Check is automatically recovered between configuration changes, but not before onCreate() finishes; so keep the next lines here\r
+ changeViewByOAuth2Check(mOAuth2Check.isChecked()); \r
+ if (mAction == ACTION_UPDATE_TOKEN && mJustCreated) {\r
+ if (mOAuth2Check.isChecked())\r
+ Toast.makeText(this, R.string.auth_expired_oauth_token_toast, Toast.LENGTH_LONG).show();\r
+ else\r
+ Toast.makeText(this, R.string.auth_expired_basic_auth_toast, Toast.LENGTH_LONG).show();\r
+ }\r
+ \r
+ if (mNewCapturedUriFromOAuth2Redirection != null) {\r
+ getOAuth2AccessTokenFromCapturedRedirection(); \r
+ }\r
+ \r
+ mJustCreated = false;\r
+ }\r
+ \r
+ \r
+ /**\r
+ * Parses the redirection with the response to the GET AUTHORIZATION request to the \r
+ * oAuth server and requests for the access token (GET ACCESS TOKEN)\r
+ */\r
+ private void getOAuth2AccessTokenFromCapturedRedirection() {\r
+ /// Parse data from OAuth redirection\r
+ String queryParameters = mNewCapturedUriFromOAuth2Redirection.getQuery();\r
+ mNewCapturedUriFromOAuth2Redirection = null;\r
+ \r
+ /// Showing the dialog with instructions for the user.\r
+ showDialog(DIALOG_OAUTH2_LOGIN_PROGRESS);\r
+\r
+ /// GET ACCESS TOKEN to the oAuth server \r
+ RemoteOperation operation = new OAuth2GetAccessToken( getString(R.string.oauth2_client_id), \r
+ getString(R.string.oauth2_redirect_uri), // TODO check - necessary here? \r
+ getString(R.string.oauth2_grant_type),\r
+ queryParameters);\r
+ //WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(getString(R.string.oauth2_url_endpoint_access)), getApplicationContext());\r
+ WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mOAuthTokenEndpointText.getText().toString().trim()), getApplicationContext());\r
+ operation.execute(client, this, mHandler);\r
+ }\r
+ \r
+\r
+ \r
+ /**\r
+ * Handles the change of focus on the text inputs for the server URL and the password\r
+ */\r
+ public void onFocusChange(View view, boolean hasFocus) {\r
+ if (view.getId() == R.id.hostUrlInput) {\r
+ onUrlInputFocusChanged((TextView) view, hasFocus);\r
+ \r
+ } else if (view.getId() == R.id.account_password) {\r
+ onPasswordFocusChanged((TextView) view, hasFocus);\r
+ }\r
+ }\r
+ \r
+\r
+ /**\r
+ * Handles changes in focus on the text input for the server URL.\r
+ * \r
+ * IMPORTANT ENTRY POINT 2: When (!hasFocus), user wrote the server URL and changed to \r
+ * other field. The operation to check the existence of the server in the entered URL is\r
+ * started. \r
+ * \r
+ * When hasFocus: user 'comes back' to write again the server URL.\r
+ * \r
+ * @param hostInput TextView with the URL input field receiving the change of focus.\r
+ * @param hasFocus 'True' if focus is received, 'false' if is lost\r
+ */\r
+ private void onUrlInputFocusChanged(TextView hostInput, boolean hasFocus) {\r
+ if (!hasFocus) {\r
+ checkOcServer();\r
+ \r
+ } else {\r
+ // avoids that the 'connect' button can be clicked if the test was previously passed\r
+ mOkButton.setEnabled(false); \r
+ }\r
+ }\r
+\r
+\r
+ private void checkOcServer() {\r
+ String uri = mHostUrlInput.getText().toString().trim();\r
+ if (uri.length() != 0) {\r
+ mStatusText = R.string.auth_testing_connection;\r
+ mStatusIcon = R.drawable.progress_small;\r
+ updateConnStatus();\r
+ /** TODO cancel previous connection check if the user tries to ammend a wrong URL \r
+ if(mConnChkOperation != null) {\r
+ mConnChkOperation.cancel();\r
+ } */\r
+ mOcServerChkOperation = new OwnCloudServerCheckOperation(uri, this);\r
+ WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(uri), this);\r
+ mHostBaseUrl = "";\r
+ mDiscoveredVersion = null;\r
+ mOperationThread = mOcServerChkOperation.execute(client, this, mHandler);\r
+ } else {\r
+ mRefreshButton.setVisibility(View.INVISIBLE);\r
+ mStatusText = 0;\r
+ mStatusIcon = 0;\r
+ updateConnStatus();\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Handles changes in focus on the text input for the password (basic authorization).\r
+ * \r
+ * When (hasFocus), the button to toggle password visibility is shown.\r
+ * \r
+ * When (!hasFocus), the button is made invisible and the password is hidden.\r
+ * \r
+ * @param passwordInput TextView with the password input field receiving the change of focus.\r
+ * @param hasFocus 'True' if focus is received, 'false' if is lost\r
+ */\r
+ private void onPasswordFocusChanged(TextView passwordInput, boolean hasFocus) {\r
+ if (hasFocus) {\r
+ mViewPasswordButton.setVisibility(View.VISIBLE);\r
+ } else {\r
+ int input_type = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD;\r
+ passwordInput.setInputType(input_type);\r
+ mViewPasswordButton.setVisibility(View.INVISIBLE);\r
+ }\r
+ }\r
+\r
+\r
+ \r
+ /**\r
+ * Cancels the authenticator activity\r
+ * \r
+ * IMPORTANT ENTRY POINT 3: Never underestimate the importance of cancellation\r
+ * \r
+ * This method is bound in the layout/acceoun_setup.xml resource file.\r
+ * \r
+ * @param view Cancel button\r
+ */\r
+ public void onCancelClick(View view) {\r
+ setResult(RESULT_CANCELED); // TODO review how is this related to AccountAuthenticator (debugging)\r
+ finish();\r
+ }\r
+ \r
+ \r
+ \r
+ /**\r
+ * Checks the credentials of the user in the root of the ownCloud server\r
+ * before creating a new local account.\r
+ * \r
+ * For basic authorization, a check of existence of the root folder is\r
+ * performed.\r
+ * \r
+ * For OAuth, starts the flow to get an access token; the credentials test \r
+ * is postponed until it is available.\r
+ * \r
+ * IMPORTANT ENTRY POINT 4\r
+ * \r
+ * @param view OK button\r
+ */\r
+ public void onOkClick(View view) {\r
+ // this check should be unnecessary\r
+ if (mDiscoveredVersion == null || !mDiscoveredVersion.isVersionValid() || mHostBaseUrl == null || mHostBaseUrl.length() == 0) {\r
+ mStatusIcon = R.drawable.common_error;\r
+ mStatusText = R.string.auth_wtf_reenter_URL;\r
+ updateConnStatus();\r
+ mOkButton.setEnabled(false);\r
- Log.d(TAG, "Starting browser to view " + uri.toString());\r
++ Log_OC.wtf(TAG, "The user was allowed to click 'connect' to an unchecked server!!");\r
+ return;\r
+ }\r
+ \r
+ if (mOAuth2Check.isChecked()) {\r
+ startOauthorization();\r
+ \r
+ } else {\r
+ checkBasicAuthorization();\r
+ }\r
+ }\r
+ \r
+ \r
+ /**\r
+ * Tests the credentials entered by the user performing a check of existence on \r
+ * the root folder of the ownCloud server.\r
+ */\r
+ private void checkBasicAuthorization() {\r
+ /// get the path to the root folder through WebDAV from the version server\r
+ String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, false);\r
+ \r
+ /// get basic credentials entered by user\r
+ String username = mUsernameInput.getText().toString();\r
+ String password = mPasswordInput.getText().toString();\r
+ \r
+ /// be gentle with the user\r
+ showDialog(DIALOG_LOGIN_PROGRESS);\r
+ \r
+ /// test credentials accessing the root folder\r
+ mAuthCheckOperation = new ExistenceCheckOperation("", this, false);\r
+ WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this);\r
+ client.setBasicCredentials(username, password);\r
+ mOperationThread = mAuthCheckOperation.execute(client, this, mHandler);\r
+ }\r
+\r
+\r
+ /**\r
+ * Starts the OAuth 'grant type' flow to get an access token, with \r
+ * a GET AUTHORIZATION request to the BUILT-IN authorization server. \r
+ */\r
+ private void startOauthorization() {\r
+ // be gentle with the user\r
+ mStatusIcon = R.drawable.progress_small;\r
+ mStatusText = R.string.oauth_login_connection;\r
+ updateAuthStatus();\r
+ \r
+ // GET AUTHORIZATION request\r
+ //Uri uri = Uri.parse(getString(R.string.oauth2_url_endpoint_auth));\r
+ Uri uri = Uri.parse(mOAuthAuthEndpointText.getText().toString().trim());\r
+ Uri.Builder uriBuilder = uri.buildUpon();\r
+ uriBuilder.appendQueryParameter(OAuth2Constants.KEY_RESPONSE_TYPE, getString(R.string.oauth2_response_type));\r
+ uriBuilder.appendQueryParameter(OAuth2Constants.KEY_REDIRECT_URI, getString(R.string.oauth2_redirect_uri)); \r
+ uriBuilder.appendQueryParameter(OAuth2Constants.KEY_CLIENT_ID, getString(R.string.oauth2_client_id));\r
+ uriBuilder.appendQueryParameter(OAuth2Constants.KEY_SCOPE, getString(R.string.oauth2_scope));\r
+ //uriBuilder.appendQueryParameter(OAuth2Constants.KEY_STATE, whateverwewant);\r
+ uri = uriBuilder.build();\r
- Log.d(TAG, "Got ACCESS TOKEN: " + mOAuthAccessToken);\r
++ Log_OC.d(TAG, "Starting browser to view " + uri.toString());\r
+ Intent i = new Intent(Intent.ACTION_VIEW, uri);\r
+ startActivity(i);\r
+ }\r
+\r
+ \r
+ /**\r
+ * Callback method invoked when a RemoteOperation executed by this Activity finishes.\r
+ * \r
+ * Dispatches the operation flow to the right method.\r
+ */\r
+ @Override\r
+ public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {\r
+\r
+ if (operation instanceof OwnCloudServerCheckOperation) {\r
+ onOcServerCheckFinish((OwnCloudServerCheckOperation) operation, result);\r
+ \r
+ } else if (operation instanceof OAuth2GetAccessToken) {\r
+ onGetOAuthAccessTokenFinish((OAuth2GetAccessToken)operation, result);\r
+ \r
+ } else if (operation instanceof ExistenceCheckOperation) {\r
+ onAuthorizationCheckFinish((ExistenceCheckOperation)operation, result);\r
+ \r
+ }\r
+ }\r
+ \r
+\r
+ /**\r
+ * Processes the result of the server check performed when the user finishes the enter of the\r
+ * server URL.\r
+ * \r
+ * @param operation Server check performed.\r
+ * @param result Result of the check.\r
+ */\r
+ private void onOcServerCheckFinish(OwnCloudServerCheckOperation operation, RemoteOperationResult result) {\r
+ /// update status icon and text\r
+ updateStatusIconAndText(result);\r
+ updateConnStatus();\r
+\r
+ /// save result state\r
+ mStatusCorrect = result.isSuccess();\r
+ mIsSslConn = (result.getCode() == ResultCode.OK_SSL);\r
+ \r
+ /// very special case (TODO: move to a common place for all the remote operations)\r
+ if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) {\r
+ mLastSslUntrustedServerResult = result;\r
+ showDialog(DIALOG_SSL_VALIDATOR); \r
+ }\r
+ \r
+ /// update the visibility of the 'retry connection' button\r
+ if (!mStatusCorrect)\r
+ mRefreshButton.setVisibility(View.VISIBLE);\r
+ else\r
+ mRefreshButton.setVisibility(View.INVISIBLE);\r
+ \r
+ /// retrieve discovered version and normalize server URL\r
+ mDiscoveredVersion = operation.getDiscoveredVersion();\r
+ mHostBaseUrl = mHostUrlInput.getText().toString().trim();\r
+ if (!mHostBaseUrl.toLowerCase().startsWith("http://") &&\r
+ !mHostBaseUrl.toLowerCase().startsWith("https://")) {\r
+ \r
+ if (mIsSslConn) {\r
+ mHostBaseUrl = "https://" + mHostBaseUrl;\r
+ } else {\r
+ mHostBaseUrl = "http://" + mHostBaseUrl;\r
+ }\r
+ \r
+ }\r
+ if (mHostBaseUrl.endsWith("/"))\r
+ mHostBaseUrl = mHostBaseUrl.substring(0, mHostBaseUrl.length() - 1);\r
+ \r
+ /// allow or not the user try to access the server\r
+ mOkButton.setEnabled(mStatusCorrect);\r
+ }\r
+\r
+\r
+ /**\r
+ * Chooses the right icon and text to show to the user for the received operation result.\r
+ * \r
+ * @param result Result of a remote operation performed in this activity\r
+ */\r
+ private void updateStatusIconAndText(RemoteOperationResult result) {\r
+ mStatusText = mStatusIcon = 0;\r
+\r
+ switch (result.getCode()) {\r
+ case OK_SSL:\r
+ mStatusIcon = android.R.drawable.ic_secure;\r
+ mStatusText = R.string.auth_secure_connection;\r
+ break;\r
+ \r
+ case OK_NO_SSL:\r
+ case OK:\r
+ if (mHostUrlInput.getText().toString().trim().toLowerCase().startsWith("http://") ) {\r
+ mStatusText = R.string.auth_connection_established;\r
+ mStatusIcon = R.drawable.ic_ok;\r
+ } else {\r
+ mStatusText = R.string.auth_nossl_plain_ok_title;\r
+ mStatusIcon = android.R.drawable.ic_partial_secure;\r
+ }\r
+ break;\r
+ \r
+ case SSL_RECOVERABLE_PEER_UNVERIFIED:\r
+ mStatusIcon = R.drawable.common_error;\r
+ mStatusText = R.string.auth_ssl_unverified_server_title;\r
+ break;\r
+ \r
+ case BAD_OC_VERSION:\r
+ mStatusIcon = R.drawable.common_error;\r
+ mStatusText = R.string.auth_bad_oc_version_title;\r
+ break;\r
+ case WRONG_CONNECTION:\r
+ mStatusIcon = R.drawable.common_error;\r
+ mStatusText = R.string.auth_wrong_connection_title;\r
+ break;\r
+ case TIMEOUT:\r
+ mStatusIcon = R.drawable.common_error;\r
+ mStatusText = R.string.auth_timeout_title;\r
+ break;\r
+ case INCORRECT_ADDRESS:\r
+ mStatusIcon = R.drawable.common_error;\r
+ mStatusText = R.string.auth_incorrect_address_title;\r
+ break;\r
+ \r
+ case SSL_ERROR:\r
+ mStatusIcon = R.drawable.common_error;\r
+ mStatusText = R.string.auth_ssl_general_error_title;\r
+ break;\r
+ \r
+ case UNAUTHORIZED:\r
+ mStatusIcon = R.drawable.common_error;\r
+ mStatusText = R.string.auth_unauthorized;\r
+ break;\r
+ case HOST_NOT_AVAILABLE:\r
+ mStatusIcon = R.drawable.common_error;\r
+ mStatusText = R.string.auth_unknown_host_title;\r
+ break;\r
+ case NO_NETWORK_CONNECTION:\r
+ mStatusIcon = R.drawable.no_network;\r
+ mStatusText = R.string.auth_no_net_conn_title;\r
+ break;\r
+ case INSTANCE_NOT_CONFIGURED:\r
+ mStatusIcon = R.drawable.common_error;\r
+ mStatusText = R.string.auth_not_configured_title;\r
+ break;\r
+ case FILE_NOT_FOUND:\r
+ mStatusIcon = R.drawable.common_error;\r
+ mStatusText = R.string.auth_incorrect_path_title;\r
+ break;\r
+ case OAUTH2_ERROR:\r
+ mStatusIcon = R.drawable.common_error;\r
+ mStatusText = R.string.auth_oauth_error;\r
+ break;\r
+ case OAUTH2_ERROR_ACCESS_DENIED:\r
+ mStatusIcon = R.drawable.common_error;\r
+ mStatusText = R.string.auth_oauth_error_access_denied;\r
+ break;\r
+ case UNHANDLED_HTTP_CODE:\r
+ case UNKNOWN_ERROR:\r
+ mStatusIcon = R.drawable.common_error;\r
+ mStatusText = R.string.auth_unknown_error_title;\r
+ break;\r
+ \r
+ default:\r
+ break;\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Processes the result of the request for and access token send \r
+ * to an OAuth authorization server.\r
+ * \r
+ * @param operation Operation performed requesting the access token.\r
+ * @param result Result of the operation.\r
+ */\r
+ private void onGetOAuthAccessTokenFinish(OAuth2GetAccessToken operation, RemoteOperationResult result) {\r
+ try {\r
+ dismissDialog(DIALOG_OAUTH2_LOGIN_PROGRESS);\r
+ } catch (IllegalArgumentException e) {\r
+ // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens\r
+ }\r
+\r
+ String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, true);\r
+ if (result.isSuccess() && webdav_path != null) {\r
+ /// be gentle with the user\r
+ showDialog(DIALOG_LOGIN_PROGRESS);\r
+ \r
+ /// time to test the retrieved access token on the ownCloud server\r
+ mOAuthAccessToken = ((OAuth2GetAccessToken)operation).getResultTokenMap().get(OAuth2Constants.KEY_ACCESS_TOKEN);\r
- Log.d(TAG, "Access failed: " + result.getLogMessage());\r
++ Log_OC.d(TAG, "Got ACCESS TOKEN: " + mOAuthAccessToken);\r
+ mAuthCheckOperation = new ExistenceCheckOperation("", this, false);\r
+ WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this);\r
+ client.setBearerCredentials(mOAuthAccessToken);\r
+ mAuthCheckOperation.execute(client, this, mHandler);\r
+ \r
+ } else {\r
+ updateStatusIconAndText(result);\r
+ updateAuthStatus();\r
- Log.d(TAG, "Successful access - time to save the account");\r
++ Log_OC.d(TAG, "Access failed: " + result.getLogMessage());\r
+ }\r
+ }\r
+\r
+ \r
+ /**\r
+ * Processes the result of the access check performed to try the user credentials.\r
+ * \r
+ * Creates a new account through the AccountManager.\r
+ * \r
+ * @param operation Access check performed.\r
+ * @param result Result of the operation.\r
+ */\r
+ private void onAuthorizationCheckFinish(ExistenceCheckOperation operation, RemoteOperationResult result) {\r
+ try {\r
+ dismissDialog(DIALOG_LOGIN_PROGRESS);\r
+ } catch (IllegalArgumentException e) {\r
+ // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens\r
+ }\r
+ \r
+ if (result.isSuccess()) {\r
- Log.d(TAG, "Access failed: " + result.getLogMessage());\r
++ Log_OC.d(TAG, "Successful access - time to save the account");\r
+\r
+ if (mAction == ACTION_CREATE) {\r
+ createAccount();\r
+ \r
+ } else {\r
+ updateToken();\r
+ }\r
+ \r
+ finish();\r
+ \r
+ } else {\r
+ updateStatusIconAndText(result);\r
+ updateAuthStatus();\r
- Log.e(TAG, "Incorrect dialog called with id = " + id);\r
++ Log_OC.d(TAG, "Access failed: " + result.getLogMessage());\r
+ }\r
+ }\r
+\r
+ \r
+ /**\r
+ * Sets the proper response to get that the Account Authenticator that started this activity saves \r
+ * a new authorization token for mAccount.\r
+ */\r
+ private void updateToken() {\r
+ Bundle response = new Bundle();\r
+ response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);\r
+ response.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type);\r
+ boolean isOAuth = mOAuth2Check.isChecked();\r
+ if (isOAuth) {\r
+ response.putString(AccountManager.KEY_AUTHTOKEN, mOAuthAccessToken);\r
+ // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention\r
+ mAccountMgr.setAuthToken(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, mOAuthAccessToken);\r
+ } else {\r
+ response.putString(AccountManager.KEY_AUTHTOKEN, mPasswordInput.getText().toString());\r
+ mAccountMgr.setPassword(mAccount, mPasswordInput.getText().toString());\r
+ }\r
+ setAccountAuthenticatorResult(response);\r
+ }\r
+\r
+\r
+ /**\r
+ * Creates a new account through the Account Authenticator that started this activity. \r
+ * \r
+ * This makes the account permanent.\r
+ * \r
+ * TODO Decide how to name the OAuth accounts\r
+ */\r
+ private void createAccount() {\r
+ /// create and save new ownCloud account\r
+ boolean isOAuth = mOAuth2Check.isChecked();\r
+ \r
+ Uri uri = Uri.parse(mHostBaseUrl);\r
+ String username = mUsernameInput.getText().toString().trim();\r
+ if (isOAuth) {\r
+ username = "OAuth_user" + (new java.util.Random(System.currentTimeMillis())).nextLong();\r
+ } \r
+ String accountName = username + "@" + uri.getHost();\r
+ if (uri.getPort() >= 0) {\r
+ accountName += ":" + uri.getPort();\r
+ }\r
+ mAccount = new Account(accountName, AccountAuthenticator.ACCOUNT_TYPE);\r
+ if (isOAuth) {\r
+ mAccountMgr.addAccountExplicitly(mAccount, "", null); // with our implementation, the password is never input in the app\r
+ } else {\r
+ mAccountMgr.addAccountExplicitly(mAccount, mPasswordInput.getText().toString(), null);\r
+ }\r
+\r
+ /// add the new account as default in preferences, if there is none already\r
+ Account defaultAccount = AccountUtils.getCurrentOwnCloudAccount(this);\r
+ if (defaultAccount == null) {\r
+ SharedPreferences.Editor editor = PreferenceManager\r
+ .getDefaultSharedPreferences(this).edit();\r
+ editor.putString("select_oc_account", accountName);\r
+ editor.commit();\r
+ }\r
+\r
+ /// prepare result to return to the Authenticator\r
+ // TODO check again what the Authenticator makes with it; probably has the same effect as addAccountExplicitly, but it's not well done\r
+ final Intent intent = new Intent(); \r
+ intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, AccountAuthenticator.ACCOUNT_TYPE);\r
+ intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);\r
+ if (!isOAuth)\r
+ intent.putExtra(AccountManager.KEY_AUTHTOKEN, AccountAuthenticator.ACCOUNT_TYPE); // TODO check this; not sure it's right; maybe\r
+ intent.putExtra(AccountManager.KEY_USERDATA, username);\r
+ if (isOAuth) {\r
+ mAccountMgr.setAuthToken(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, mOAuthAccessToken);\r
+ }\r
+ /// add user data to the new account; TODO probably can be done in the last parameter addAccountExplicitly, or in KEY_USERDATA\r
+ mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION, mDiscoveredVersion.toString());\r
+ mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL, mHostBaseUrl);\r
+ if (isOAuth)\r
+ mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2, "TRUE"); // TODO this flag should be unnecessary\r
+ \r
+ setAccountAuthenticatorResult(intent.getExtras());\r
+ setResult(RESULT_OK, intent);\r
+ \r
+ /// immediately request for the synchronization of the new account\r
+ Bundle bundle = new Bundle();\r
+ bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);\r
+ ContentResolver.requestSync(mAccount, AccountAuthenticator.AUTHORITY, bundle);\r
+ }\r
+\r
+\r
+ /**\r
+ * {@inheritDoc}\r
+ * \r
+ * Necessary to update the contents of the SSL Dialog\r
+ * \r
+ * TODO move to some common place for all possible untrusted SSL failures\r
+ */\r
+ @Override\r
+ protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {\r
+ switch (id) {\r
+ case DIALOG_LOGIN_PROGRESS:\r
+ case DIALOG_CERT_NOT_SAVED:\r
+ case DIALOG_OAUTH2_LOGIN_PROGRESS:\r
+ break;\r
+ case DIALOG_SSL_VALIDATOR: {\r
+ ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult);\r
+ break;\r
+ }\r
+ default:\r
- Log.i(TAG, "Login canceled");\r
++ Log_OC.e(TAG, "Incorrect dialog called with id = " + id);\r
+ }\r
+ }\r
+\r
+ \r
+ /**\r
+ * {@inheritDoc}\r
+ */\r
+ @Override\r
+ protected Dialog onCreateDialog(int id) {\r
+ Dialog dialog = null;\r
+ switch (id) {\r
+ case DIALOG_LOGIN_PROGRESS: {\r
+ /// simple progress dialog\r
+ ProgressDialog working_dialog = new ProgressDialog(this);\r
+ working_dialog.setMessage(getResources().getString(R.string.auth_trying_to_login));\r
+ working_dialog.setIndeterminate(true);\r
+ working_dialog.setCancelable(true);\r
+ working_dialog\r
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {\r
+ @Override\r
+ public void onCancel(DialogInterface dialog) {\r
+ /// TODO study if this is enough\r
- Log.i(TAG, "Login canceled");\r
++ Log_OC.i(TAG, "Login canceled");\r
+ if (mOperationThread != null) {\r
+ mOperationThread.interrupt();\r
+ finish();\r
+ }\r
+ }\r
+ });\r
+ dialog = working_dialog;\r
+ break;\r
+ }\r
+ case DIALOG_OAUTH2_LOGIN_PROGRESS: {\r
+ ProgressDialog working_dialog = new ProgressDialog(this);\r
+ working_dialog.setMessage(String.format("Getting authorization")); \r
+ working_dialog.setIndeterminate(true);\r
+ working_dialog.setCancelable(true);\r
+ working_dialog\r
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {\r
+ @Override\r
+ public void onCancel(DialogInterface dialog) {\r
- Log.e(TAG, "Incorrect dialog called with id = " + id);\r
++ Log_OC.i(TAG, "Login canceled");\r
+ finish();\r
+ }\r
+ });\r
+ dialog = working_dialog;\r
+ break;\r
+ }\r
+ case DIALOG_SSL_VALIDATOR: {\r
+ /// TODO start to use new dialog interface, at least for this (it is a FragmentDialog already)\r
+ dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this);\r
+ break;\r
+ }\r
+ case DIALOG_CERT_NOT_SAVED: {\r
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);\r
+ builder.setMessage(getResources().getString(R.string.ssl_validator_not_saved));\r
+ builder.setCancelable(false);\r
+ builder.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {\r
+ @Override\r
+ public void onClick(DialogInterface dialog, int which) {\r
+ dialog.dismiss();\r
+ };\r
+ });\r
+ dialog = builder.create();\r
+ break;\r
+ }\r
+ default:\r
++ Log_OC.e(TAG, "Incorrect dialog called with id = " + id);\r
+ }\r
+ return dialog;\r
+ }\r
+\r
+ \r
+ /**\r
+ * Starts and activity to open the 'new account' page in the ownCloud web site\r
+ * \r
+ * @param view 'Account register' button\r
+ */\r
+ public void onRegisterClick(View view) {\r
+ Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_account_register)));\r
+ setResult(RESULT_CANCELED);\r
+ startActivity(register);\r
+ }\r
+\r
+ \r
+ /**\r
+ * Updates the content and visibility state of the icon and text associated\r
+ * to the last check on the ownCloud server.\r
+ */\r
+ private void updateConnStatus() {\r
+ ImageView iv = (ImageView) findViewById(R.id.action_indicator);\r
+ TextView tv = (TextView) findViewById(R.id.status_text);\r
+\r
+ if (mStatusIcon == 0 && mStatusText == 0) {\r
+ iv.setVisibility(View.INVISIBLE);\r
+ tv.setVisibility(View.INVISIBLE);\r
+ } else {\r
+ iv.setImageResource(mStatusIcon);\r
+ tv.setText(mStatusText);\r
+ iv.setVisibility(View.VISIBLE);\r
+ tv.setVisibility(View.VISIBLE);\r
+ }\r
+ }\r
+ \r
+ \r
+ /**\r
+ * Updates the content and visibility state of the icon and text associated\r
+ * to the interactions with the OAuth authorization server.\r
+ */\r
+ private void updateAuthStatus() {\r
+ if (mStatusIcon == 0 && mStatusText == 0) {\r
+ mAuthStatusLayout.setVisibility(View.INVISIBLE);\r
+ } else {\r
+ mAuthStatusLayout.setText(mStatusText);\r
+ mAuthStatusLayout.setCompoundDrawablesWithIntrinsicBounds(mStatusIcon, 0, 0, 0);\r
+ mAuthStatusLayout.setVisibility(View.VISIBLE);\r
+ }\r
+ } \r
+\r
+ \r
+ /**\r
+ * Called when the refresh button in the input field for ownCloud host is clicked.\r
+ * \r
+ * Performs a new check on the URL in the input field.\r
+ * \r
+ * @param view Refresh 'button'\r
+ */\r
+ public void onRefreshClick(View view) {\r
+ onFocusChange(mRefreshButton, false);\r
+ }\r
+ \r
+ \r
+ /**\r
+ * Called when the eye icon in the password field is clicked.\r
+ * \r
+ * Toggles the visibility of the password in the field. \r
+ * \r
+ * @param view 'View password' 'button'\r
+ */\r
+ public void onViewPasswordClick(View view) {\r
+ int selectionStart = mPasswordInput.getSelectionStart();\r
+ int selectionEnd = mPasswordInput.getSelectionEnd();\r
+ int input_type = mPasswordInput.getInputType();\r
+ if ((input_type & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {\r
+ input_type = InputType.TYPE_CLASS_TEXT\r
+ | InputType.TYPE_TEXT_VARIATION_PASSWORD;\r
+ } else {\r
+ input_type = InputType.TYPE_CLASS_TEXT\r
+ | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;\r
+ }\r
+ mPasswordInput.setInputType(input_type);\r
+ mPasswordInput.setSelection(selectionStart, selectionEnd);\r
+ } \r
+ \r
+ \r
+ /**\r
+ * Called when the checkbox for OAuth authorization is clicked.\r
+ * \r
+ * Hides or shows the input fields for user & password. \r
+ * \r
+ * @param view 'View password' 'button'\r
+ */\r
+ public void onCheckClick(View view) {\r
+ CheckBox oAuth2Check = (CheckBox)view; \r
+ changeViewByOAuth2Check(oAuth2Check.isChecked());\r
+\r
+ }\r
+ \r
+ /**\r
+ * Changes the visibility of input elements depending upon the kind of authorization\r
+ * chosen by the user: basic or OAuth\r
+ * \r
+ * @param checked 'True' when OAuth is selected.\r
+ */\r
+ public void changeViewByOAuth2Check(Boolean checked) {\r
+ \r
+ if (checked) {\r
+ mOAuthAuthEndpointText.setVisibility(View.VISIBLE);\r
+ mOAuthTokenEndpointText.setVisibility(View.VISIBLE);\r
+ mUsernameInput.setVisibility(View.GONE);\r
+ mPasswordInput.setVisibility(View.GONE);\r
+ mViewPasswordButton.setVisibility(View.GONE);\r
+ } else {\r
+ mOAuthAuthEndpointText.setVisibility(View.GONE);\r
+ mOAuthTokenEndpointText.setVisibility(View.GONE);\r
+ mUsernameInput.setVisibility(View.VISIBLE);\r
+ mPasswordInput.setVisibility(View.VISIBLE);\r
+ mViewPasswordButton.setVisibility(View.INVISIBLE);\r
+ } \r
+\r
+ } \r
+ \r
+ /**\r
+ * Called from SslValidatorDialog when a new server certificate was correctly saved.\r
+ */\r
+ public void onSavedCertificate() {\r
+ mOperationThread = mOcServerChkOperation.retry(this, mHandler); \r
+ }\r
+\r
+ /**\r
+ * Called from SslValidatorDialog when a new server certificate could not be saved \r
+ * when the user requested it.\r
+ */\r
+ @Override\r
+ public void onFailedSavingCertificate() {\r
+ showDialog(DIALOG_CERT_NOT_SAVED);\r
+ }\r
+\r
+}\r
--- /dev/null
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+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";
+
+}
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
--import android.util.Log;
public class FileDataStorageManager implements DataStorageManager {
- /* ownCloud Android client application\r
- * Copyright (C) 2011-2012 Bartek Przybylski\r
- *\r
- * This program is free software: you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 2 of the License, or\r
- * (at your option) any later version.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program. If not, see <http://www.gnu.org/licenses/>.\r
- *\r
- */\r
- package com.owncloud.android.db;\r
- \r
- import android.content.ContentValues;\r
- import android.content.Context;\r
- import android.database.Cursor;\r
- import android.database.sqlite.SQLiteDatabase;\r
- import android.database.sqlite.SQLiteOpenHelper;\r
- \r
- /**\r
- * Custom database helper for ownCloud\r
- * \r
- * @author Bartek Przybylski\r
- * \r
- */\r
- public class DbHandler {\r
- private SQLiteDatabase mDB;\r
- private OpenerHelper mHelper;\r
- private final String mDatabaseName = "ownCloud";\r
- private final int mDatabaseVersion = 1;\r
- \r
- private final String TABLE_INSTANT_UPLOAD = "instant_upload";\r
- \r
- public DbHandler(Context context) {\r
- mHelper = new OpenerHelper(context);\r
- mDB = mHelper.getWritableDatabase();\r
- }\r
- \r
- public void close() {\r
- mDB.close();\r
- }\r
- \r
- public boolean putFileForLater(String filepath, String account) {\r
- ContentValues cv = new ContentValues();\r
- cv.put("path", filepath);\r
- cv.put("account", account);\r
- return mDB.insert(TABLE_INSTANT_UPLOAD, null, cv) != -1;\r
- }\r
- \r
- public Cursor getAwaitingFiles() {\r
- return mDB.query(TABLE_INSTANT_UPLOAD, null, null, null, null, null, null);\r
- }\r
- \r
- public void clearFiles() {\r
- mDB.delete(TABLE_INSTANT_UPLOAD, null, null);\r
- }\r
- \r
- /**\r
- * \r
- * @param localPath\r
- * @param accountName\r
- * @return true when one or more pendin files was removed\r
- */\r
- public boolean removeIUPendingFile(String localPath, String accountName) {\r
- return mDB.delete(TABLE_INSTANT_UPLOAD,\r
- "path = ?",\r
- new String[]{ localPath }) != 0;\r
- \r
- }\r
- \r
- private class OpenerHelper extends SQLiteOpenHelper {\r
- public OpenerHelper(Context context) {\r
- super(context, mDatabaseName, null, mDatabaseVersion);\r
- }\r
- \r
- @Override\r
- public void onCreate(SQLiteDatabase db) {\r
- db.execSQL("CREATE TABLE " + TABLE_INSTANT_UPLOAD + " ("\r
- + " _id INTEGER PRIMARY KEY, "\r
- + " path TEXT,"\r
- + " account TEXT);");\r
- }\r
- \r
- @Override\r
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\r
- }\r
- }\r
- }\r
+ /* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+ package com.owncloud.android.db;
+
+ import com.owncloud.android.Log_OC;
+
+ import android.content.ContentValues;
+ import android.content.Context;
+ import android.database.Cursor;
+ import android.database.sqlite.SQLiteDatabase;
+ import android.database.sqlite.SQLiteOpenHelper;
-import android.util.Log;
+
+ /**
+ * 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();
+ }
+
+ 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;");
+
+ }
+ }
+ }
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
--import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.app.ListActivity;
import android.os.Bundle;
import android.os.Handler;
--import android.util.Log;
import android.widget.SimpleAdapter;
public class ExtensionsListActivity extends ListActivity {
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
--import android.util.Log;
public class BootupBroadcastReceiver extends BroadcastReceiver {
import android.net.NetworkInfo.State;
import android.preference.PreferenceManager;
import android.provider.MediaStore.Images.Media;
- import android.util.Log;
import android.webkit.MimeTypeMap;
-import com.owncloud.android.AccountUtils;
+ import com.owncloud.android.Log_OC;
-import com.owncloud.android.authenticator.AccountAuthenticator;
-import com.owncloud.android.db.DbHandler;
-import com.owncloud.android.files.services.FileUploader;
+ import com.owncloud.android.utils.FileStorageUtils;
+
public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
- public static String INSTANT_UPLOAD_DIR = "/InstantUpload/";
private static String TAG = "PhotoTakenBroadcastReceiver";
- private static final String[] CONTENT_PROJECTION = { Media.DATA,
- Media.DISPLAY_NAME,
- Media.MIME_TYPE,
- Media.SIZE };
+ private static final String[] CONTENT_PROJECTION = { Media.DATA, Media.DISPLAY_NAME, Media.MIME_TYPE, Media.SIZE };
private static String NEW_PHOTO_ACTION = "com.android.camera.NEW_PICTURE";
-
+
@Override
public void onReceive(Context context, Intent intent) {
- Log.d(TAG, "Received: " + intent.getAction());
+ Log_OC.d(TAG, "Received: " + intent.getAction());
if (intent.getAction().equals(android.net.ConnectivityManager.CONNECTIVITY_ACTION)) {
handleConnectivityAction(context, intent);
} else if (intent.getAction().equals(NEW_PHOTO_ACTION)) {
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.network.OwnCloudClientUtils;
import com.owncloud.android.operations.RemoteOperationResult;
import com.owncloud.android.operations.SynchronizeFileOperation;
import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
import android.content.Context;
import android.content.Intent;
import android.os.FileObserver;
--import android.util.Log;
public class OwnCloudFileObserver extends FileObserver {
- /* ownCloud Android client application\r
- * Copyright (C) 2012 Bartek Przybylski\r
- * Copyright (C) 2012-2013 ownCloud Inc.\r
- *\r
- * This program is free software: you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 2 of the License, or\r
- * (at your option) any later version.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program. If not, see <http://www.gnu.org/licenses/>.\r
- *\r
- */\r
- \r
- package com.owncloud.android.files.services;\r
- \r
- import java.io.File;\r
- import java.io.IOException;\r
- import java.util.AbstractList;\r
- import java.util.Iterator;\r
- import java.util.Vector;\r
- import java.util.concurrent.ConcurrentHashMap;\r
- import java.util.concurrent.ConcurrentMap;\r
- \r
- import com.owncloud.android.authentication.AuthenticatorActivity;\r
- import com.owncloud.android.datamodel.FileDataStorageManager;\r
- import com.owncloud.android.datamodel.OCFile;\r
- import eu.alefzero.webdav.OnDatatransferProgressListener;\r
- \r
- import com.owncloud.android.network.OwnCloudClientUtils;\r
- import com.owncloud.android.operations.DownloadFileOperation;\r
- import com.owncloud.android.operations.RemoteOperationResult;\r
- import com.owncloud.android.operations.RemoteOperationResult.ResultCode;\r
- import com.owncloud.android.ui.activity.FileDetailActivity;\r
- import com.owncloud.android.ui.fragment.FileDetailFragment;\r
- \r
- import android.accounts.Account;\r
- import android.accounts.AccountsException;\r
- import android.app.Notification;\r
- import android.app.NotificationManager;\r
- import android.app.PendingIntent;\r
- import android.app.Service;\r
- import android.content.Intent;\r
- import android.os.Binder;\r
- import android.os.Handler;\r
- import android.os.HandlerThread;\r
- import android.os.IBinder;\r
- import android.os.Looper;\r
- import android.os.Message;\r
- import android.os.Process;\r
- import android.util.Log;\r
- import android.widget.RemoteViews;\r
- \r
- import com.owncloud.android.R;\r
- import eu.alefzero.webdav.WebdavClient;\r
- \r
- public class FileDownloader extends Service implements OnDatatransferProgressListener {\r
- \r
- public static final String EXTRA_ACCOUNT = "ACCOUNT";\r
- public static final String EXTRA_FILE = "FILE";\r
- \r
- public static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED";\r
- public static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH";\r
- public static final String EXTRA_DOWNLOAD_RESULT = "RESULT"; \r
- public static final String EXTRA_FILE_PATH = "FILE_PATH";\r
- public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";\r
- public static final String ACCOUNT_NAME = "ACCOUNT_NAME";\r
- \r
- private static final String TAG = "FileDownloader";\r
- \r
- private Looper mServiceLooper;\r
- private ServiceHandler mServiceHandler;\r
- private IBinder mBinder;\r
- private WebdavClient mDownloadClient = null;\r
- private Account mLastAccount = null;\r
- private FileDataStorageManager mStorageManager;\r
- \r
- private ConcurrentMap<String, DownloadFileOperation> mPendingDownloads = new ConcurrentHashMap<String, DownloadFileOperation>();\r
- private DownloadFileOperation mCurrentDownload = null;\r
- \r
- private NotificationManager mNotificationManager;\r
- private Notification mNotification;\r
- private int mLastPercent;\r
- \r
- \r
- /**\r
- * Builds a key for mPendingDownloads from the account and file to download\r
- * \r
- * @param account Account where the file to download is stored\r
- * @param file File to download\r
- */\r
- private String buildRemoteName(Account account, OCFile file) {\r
- return account.name + file.getRemotePath();\r
- }\r
- \r
- \r
- /**\r
- * Service initialization\r
- */\r
- @Override\r
- public void onCreate() {\r
- super.onCreate();\r
- mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);\r
- HandlerThread thread = new HandlerThread("FileDownloaderThread",\r
- Process.THREAD_PRIORITY_BACKGROUND);\r
- thread.start();\r
- mServiceLooper = thread.getLooper();\r
- mServiceHandler = new ServiceHandler(mServiceLooper, this);\r
- mBinder = new FileDownloaderBinder();\r
- }\r
- \r
- \r
- /**\r
- * Entry point to add one or several files to the queue of downloads.\r
- * \r
- * New downloads are added calling to startService(), resulting in a call to this method. This ensures the service will keep on working \r
- * although the caller activity goes away.\r
- */\r
- @Override\r
- public int onStartCommand(Intent intent, int flags, int startId) {\r
- if ( !intent.hasExtra(EXTRA_ACCOUNT) ||\r
- !intent.hasExtra(EXTRA_FILE)\r
- /*!intent.hasExtra(EXTRA_FILE_PATH) ||\r
- !intent.hasExtra(EXTRA_REMOTE_PATH)*/\r
- ) {\r
- Log.e(TAG, "Not enough information provided in intent");\r
- return START_NOT_STICKY;\r
- }\r
- Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);\r
- OCFile file = intent.getParcelableExtra(EXTRA_FILE);\r
- \r
- AbstractList<String> requestedDownloads = new Vector<String>(); // dvelasco: now this always contains just one element, but that can change in a near future (download of multiple selection)\r
- String downloadKey = buildRemoteName(account, file);\r
- try {\r
- DownloadFileOperation newDownload = new DownloadFileOperation(account, file); \r
- mPendingDownloads.putIfAbsent(downloadKey, newDownload);\r
- newDownload.addDatatransferProgressListener(this);\r
- requestedDownloads.add(downloadKey);\r
- sendBroadcastNewDownload(newDownload);\r
- \r
- } catch (IllegalArgumentException e) {\r
- Log.e(TAG, "Not enough information provided in intent: " + e.getMessage());\r
- return START_NOT_STICKY;\r
- }\r
- \r
- if (requestedDownloads.size() > 0) {\r
- Message msg = mServiceHandler.obtainMessage();\r
- msg.arg1 = startId;\r
- msg.obj = requestedDownloads;\r
- mServiceHandler.sendMessage(msg);\r
- }\r
- \r
- return START_NOT_STICKY;\r
- }\r
- \r
- \r
- /**\r
- * Provides a binder object that clients can use to perform operations on the queue of downloads, excepting the addition of new files. \r
- * \r
- * Implemented to perform cancellation, pause and resume of existing downloads.\r
- */\r
- @Override\r
- public IBinder onBind(Intent arg0) {\r
- return mBinder;\r
- }\r
- \r
- \r
- /**\r
- * Binder to let client components to perform operations on the queue of downloads.\r
- * \r
- * It provides by itself the available operations.\r
- */\r
- public class FileDownloaderBinder extends Binder {\r
- \r
- /**\r
- * Cancels a pending or current download of a remote file.\r
- * \r
- * @param account Owncloud account where the remote file is stored.\r
- * @param file A file in the queue of pending downloads\r
- */\r
- public void cancel(Account account, OCFile file) {\r
- DownloadFileOperation download = null;\r
- synchronized (mPendingDownloads) {\r
- download = mPendingDownloads.remove(buildRemoteName(account, file));\r
- }\r
- if (download != null) {\r
- download.cancel();\r
- }\r
- }\r
- \r
- \r
- /**\r
- * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download.\r
- * \r
- * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download. \r
- * \r
- * @param account Owncloud account where the remote file is stored.\r
- * @param file A file that could be in the queue of downloads.\r
- */\r
- public boolean isDownloading(Account account, OCFile file) {\r
- if (account == null || file == null) return false;\r
- String targetKey = buildRemoteName(account, file);\r
- synchronized (mPendingDownloads) {\r
- if (file.isDirectory()) {\r
- // this can be slow if there are many downloads :(\r
- Iterator<String> it = mPendingDownloads.keySet().iterator();\r
- boolean found = false;\r
- while (it.hasNext() && !found) {\r
- found = it.next().startsWith(targetKey);\r
- }\r
- return found;\r
- } else {\r
- return (mPendingDownloads.containsKey(targetKey));\r
- }\r
- }\r
- }\r
- }\r
- \r
- \r
- /** \r
- * Download worker. Performs the pending downloads in the order they were requested. \r
- * \r
- * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}. \r
- */\r
- private static class ServiceHandler extends Handler {\r
- // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak\r
- FileDownloader mService;\r
- public ServiceHandler(Looper looper, FileDownloader service) {\r
- super(looper);\r
- if (service == null)\r
- throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");\r
- mService = service;\r
- }\r
- \r
- @Override\r
- public void handleMessage(Message msg) {\r
- @SuppressWarnings("unchecked")\r
- AbstractList<String> requestedDownloads = (AbstractList<String>) msg.obj;\r
- if (msg.obj != null) {\r
- Iterator<String> it = requestedDownloads.iterator();\r
- while (it.hasNext()) {\r
- mService.downloadFile(it.next());\r
- }\r
- }\r
- mService.stopSelf(msg.arg1);\r
- }\r
- }\r
- \r
- \r
- \r
- /**\r
- * Core download method: requests a file to download and stores it.\r
- * \r
- * @param downloadKey Key to access the download to perform, contained in mPendingDownloads \r
- */\r
- private void downloadFile(String downloadKey) {\r
- \r
- synchronized(mPendingDownloads) {\r
- mCurrentDownload = mPendingDownloads.get(downloadKey);\r
- }\r
- \r
- if (mCurrentDownload != null) {\r
- \r
- notifyDownloadStart(mCurrentDownload);\r
- \r
- RemoteOperationResult downloadResult = null;\r
- try {\r
- /// prepare client object to send the request to the ownCloud server\r
- if (mDownloadClient == null || !mLastAccount.equals(mCurrentDownload.getAccount())) {\r
- mLastAccount = mCurrentDownload.getAccount();\r
- mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver());\r
- mDownloadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext());\r
- }\r
- \r
- /// perform the download\r
- if (downloadResult == null) {\r
- downloadResult = mCurrentDownload.execute(mDownloadClient);\r
- }\r
- if (downloadResult.isSuccess()) {\r
- saveDownloadedFile();\r
- }\r
- \r
- } catch (AccountsException e) {\r
- Log.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);\r
- downloadResult = new RemoteOperationResult(e);\r
- } catch (IOException e) {\r
- Log.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);\r
- downloadResult = new RemoteOperationResult(e);\r
- \r
- } finally {\r
- synchronized(mPendingDownloads) {\r
- mPendingDownloads.remove(downloadKey);\r
- }\r
- }\r
- \r
- \r
- /// notify result\r
- notifyDownloadResult(mCurrentDownload, downloadResult);\r
- \r
- sendBroadcastDownloadFinished(mCurrentDownload, downloadResult);\r
- }\r
- }\r
- \r
- \r
- /**\r
- * Updates the OC File after a successful download.\r
- */\r
- private void saveDownloadedFile() {\r
- OCFile file = mCurrentDownload.getFile();\r
- long syncDate = System.currentTimeMillis();\r
- file.setLastSyncDateForProperties(syncDate);\r
- file.setLastSyncDateForData(syncDate);\r
- file.setModificationTimestamp(mCurrentDownload.getModificationTimestamp());\r
- file.setModificationTimestampAtLastSyncForData(mCurrentDownload.getModificationTimestamp());\r
- // file.setEtag(mCurrentDownload.getEtag()); // TODO Etag, where available\r
- file.setMimetype(mCurrentDownload.getMimeType());\r
- file.setStoragePath(mCurrentDownload.getSavePath());\r
- file.setFileLength((new File(mCurrentDownload.getSavePath()).length()));\r
- mStorageManager.saveFile(file);\r
- }\r
- \r
- \r
- /**\r
- * Creates a status notification to show the download progress\r
- * \r
- * @param download Download operation starting.\r
- */\r
- private void notifyDownloadStart(DownloadFileOperation download) {\r
- /// create status notification with a progress bar\r
- mLastPercent = 0;\r
- mNotification = new Notification(R.drawable.icon, getString(R.string.downloader_download_in_progress_ticker), System.currentTimeMillis());\r
- mNotification.flags |= Notification.FLAG_ONGOING_EVENT;\r
- mNotification.contentView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.progressbar_layout);\r
- mNotification.contentView.setProgressBar(R.id.status_progress, 100, 0, download.getSize() < 0);\r
- mNotification.contentView.setTextViewText(R.id.status_text, String.format(getString(R.string.downloader_download_in_progress_content), 0, new File(download.getSavePath()).getName()));\r
- mNotification.contentView.setImageViewResource(R.id.status_icon, R.drawable.icon);\r
- \r
- /// includes a pending intent in the notification showing the details view of the file\r
- Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);\r
- showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, download.getFile());\r
- showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, download.getAccount());\r
- showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);\r
- mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), showDetailsIntent, 0);\r
- \r
- mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotification);\r
- }\r
- \r
- \r
- /**\r
- * Callback method to update the progress bar in the status notification.\r
- */\r
- @Override\r
- public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String fileName) {\r
- int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer));\r
- if (percent != mLastPercent) {\r
- mNotification.contentView.setProgressBar(R.id.status_progress, 100, percent, totalToTransfer < 0);\r
- String text = String.format(getString(R.string.downloader_download_in_progress_content), percent, fileName);\r
- mNotification.contentView.setTextViewText(R.id.status_text, text);\r
- mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotification);\r
- }\r
- mLastPercent = percent;\r
- }\r
- \r
- \r
- /**\r
- * Callback method to update the progress bar in the status notification (old version)\r
- */\r
- @Override\r
- public void onTransferProgress(long progressRate) {\r
- // NOTHING TO DO HERE ANYMORE\r
- }\r
- \r
- \r
- /**\r
- * Updates the status notification with the result of a download operation.\r
- * \r
- * @param downloadResult Result of the download operation.\r
- * @param download Finished download operation\r
- */\r
- private void notifyDownloadResult(DownloadFileOperation download, RemoteOperationResult downloadResult) {\r
- mNotificationManager.cancel(R.string.downloader_download_in_progress_ticker);\r
- if (!downloadResult.isCancelled()) {\r
- int tickerId = (downloadResult.isSuccess()) ? R.string.downloader_download_succeeded_ticker : R.string.downloader_download_failed_ticker;\r
- int contentId = (downloadResult.isSuccess()) ? R.string.downloader_download_succeeded_content : R.string.downloader_download_failed_content;\r
- Notification finalNotification = new Notification(R.drawable.icon, getString(tickerId), System.currentTimeMillis());\r
- finalNotification.flags |= Notification.FLAG_AUTO_CANCEL;\r
- boolean needsToUpdateCredentials = (downloadResult.getCode() == ResultCode.UNAUTHORIZED);\r
- if (needsToUpdateCredentials) {\r
- // let the user update credentials with one click\r
- Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);\r
- updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, download.getAccount());\r
- updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_TOKEN);\r
- updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\r
- updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);\r
- updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);\r
- finalNotification.contentIntent = PendingIntent.getActivity(this, (int)System.currentTimeMillis(), updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT);\r
- finalNotification.setLatestEventInfo( getApplicationContext(), \r
- getString(tickerId), \r
- String.format(getString(contentId), new File(download.getSavePath()).getName()),\r
- finalNotification.contentIntent);\r
- mDownloadClient = null; // grant that future retries on the same account will get the fresh credentials\r
- \r
- } else {\r
- // TODO put something smart in the contentIntent below\r
- finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);\r
- finalNotification.setLatestEventInfo(getApplicationContext(), getString(tickerId), String.format(getString(contentId), new File(download.getSavePath()).getName()), finalNotification.contentIntent);\r
- }\r
- mNotificationManager.notify(tickerId, finalNotification);\r
- }\r
- }\r
- \r
- \r
- /**\r
- * Sends a broadcast when a download finishes in order to the interested activities can update their view\r
- * \r
- * @param download Finished download operation\r
- * @param downloadResult Result of the download operation\r
- */\r
- private void sendBroadcastDownloadFinished(DownloadFileOperation download, RemoteOperationResult downloadResult) {\r
- Intent end = new Intent(DOWNLOAD_FINISH_MESSAGE);\r
- end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess());\r
- end.putExtra(ACCOUNT_NAME, download.getAccount().name);\r
- end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());\r
- end.putExtra(EXTRA_FILE_PATH, download.getSavePath());\r
- sendStickyBroadcast(end);\r
- }\r
- \r
- \r
- /**\r
- * Sends a broadcast when a new download is added to the queue.\r
- * \r
- * @param download Added download operation\r
- */\r
- private void sendBroadcastNewDownload(DownloadFileOperation download) {\r
- Intent added = new Intent(DOWNLOAD_ADDED_MESSAGE);\r
- /*added.putExtra(ACCOUNT_NAME, download.getAccount().name);\r
- added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());*/\r
- added.putExtra(EXTRA_FILE_PATH, download.getSavePath());\r
- sendStickyBroadcast(added);\r
- }\r
- \r
- }\r
+ /* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+ 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.authentication.AuthenticatorActivity;
+ import com.owncloud.android.datamodel.FileDataStorageManager;
+ import com.owncloud.android.datamodel.OCFile;
+ import eu.alefzero.webdav.OnDatatransferProgressListener;
+
+ 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.FileDetailActivity;
+ import com.owncloud.android.ui.fragment.FileDetailFragment;
+ import com.owncloud.android.ui.preview.PreviewImageActivity;
+ import com.owncloud.android.ui.preview.PreviewImageFragment;
+
+ 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 com.owncloud.android.Log_OC;
+ import com.owncloud.android.R;
+ 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";
+
+ public static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED";
+ public 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<String, DownloadFileOperation> mPendingDownloads = new ConcurrentHashMap<String, DownloadFileOperation>();
+ private DownloadFileOperation mCurrentDownload = null;
+
+ private NotificationManager mNotificationManager;
+ private Notification mNotification;
+ private int mLastPercent;
+
+
+ /**
+ * 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<String> requestedDownloads = new Vector<String>(); // 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<String, OnDatatransferProgressListener> mBoundListeners = new HashMap<String, OnDatatransferProgressListener>();
+
+
+ /**
+ * 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<String> 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<String> requestedDownloads = (AbstractList<String>) msg.obj;
+ if (msg.obj != null) {
+ Iterator<String> 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);
+
- /// 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
+ 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, FileDetailActivity.class);
+ }
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, download.getFile());
+ showDetailsIntent.putExtra(FileDetailFragment.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;
- Intent showDetailsIntent = null;
- if (downloadResult.isSuccess()) {
- if (PreviewImageFragment.canBePreviewed(download.getFile())) {
- showDetailsIntent = new Intent(this, PreviewImageActivity.class);
- } else {
- showDetailsIntent = new Intent(this, FileDetailActivity.class);
- }
- showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, download.getFile());
- showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, download.getAccount());
- showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
++ boolean needsToUpdateCredentials = (downloadResult.getCode() == ResultCode.UNAUTHORIZED);
++ 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_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 {
- // TODO put something smart in showDetailsIntent
- showDetailsIntent = new Intent();
++ Intent showDetailsIntent = null;
++ if (downloadResult.isSuccess()) {
++ if (PreviewImageFragment.canBePreviewed(download.getFile())) {
++ showDetailsIntent = new Intent(this, PreviewImageActivity.class);
++ } else {
++ showDetailsIntent = new Intent(this, FileDetailActivity.class);
++ }
++ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, download.getFile());
++ showDetailsIntent.putExtra(FileDetailFragment.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);
+ }
- 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(DOWNLOAD_FINISH_MESSAGE);
+ 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(DOWNLOAD_ADDED_MESSAGE);
+ added.putExtra(ACCOUNT_NAME, download.getAccount().name);
+ added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
+ added.putExtra(EXTRA_FILE_PATH, download.getSavePath());
+ sendStickyBroadcast(added);
+ }
+
+ }
import android.database.Cursor;
import android.os.Binder;
import android.os.IBinder;
--import android.util.Log;
public class FileObserverService extends Service {
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.jackrabbit.webdav.MultiStatus;
import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
- import com.owncloud.android.files.InstantUploadBroadcastReceiver;
+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.operations.ChunkedUploadFileOperation;
+import com.owncloud.android.operations.CreateFolderOperation;
+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.FileDetailActivity;
+import com.owncloud.android.ui.fragment.FileDetailFragment;
+import com.owncloud.android.utils.OwnCloudVersion;
+
+import eu.alefzero.webdav.OnDatatransferProgressListener;
+import eu.alefzero.webdav.WebdavEntry;
+import eu.alefzero.webdav.WebdavUtils;
+
+import com.owncloud.android.network.OwnCloudClientUtils;
+
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.os.Looper;
import android.os.Message;
import android.os.Process;
- import android.util.Log;
import android.webkit.MimeTypeMap;
import android.widget.RemoteViews;
-import android.widget.Toast;
+ import com.owncloud.android.Log_OC;
import com.owncloud.android.R;
-import com.owncloud.android.authenticator.AccountAuthenticator;
-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.RemoteOperationResult;
-import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
-import com.owncloud.android.operations.UploadFileOperation;
+ import com.owncloud.android.ui.activity.FailedUploadActivity;
-import com.owncloud.android.ui.activity.FileDetailActivity;
+ import com.owncloud.android.ui.activity.InstantUploadActivity;
-import com.owncloud.android.ui.fragment.FileDetailFragment;
+ import com.owncloud.android.ui.preview.PreviewImageActivity;
+ import com.owncloud.android.ui.preview.PreviewImageFragment;
+ import com.owncloud.android.utils.FileStorageUtils;
-import com.owncloud.android.utils.OwnCloudVersion;
+
-import eu.alefzero.webdav.OnDatatransferProgressListener;
import eu.alefzero.webdav.WebdavClient;
-import eu.alefzero.webdav.WebdavEntry;
-import eu.alefzero.webdav.WebdavUtils;
public class FileUploader extends Service implements OnDatatransferProgressListener {
*/
public void uploadFile(String uploadKey) {
- synchronized(mPendingUploads) {
+ synchronized (mPendingUploads) {
mCurrentUpload = mPendingUploads.get(uploadKey);
}
-
+
if (mCurrentUpload != null) {
-
+
notifyUploadStart(mCurrentUpload);
- // / 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());
- }
-
- // / create remote folder for instant uploads
- if (mCurrentUpload.isRemoteFolderToBeCreated()) {
- mUploadClient.createDirectory(FileStorageUtils.getInstantUploadFilePath(this, ""));
- // ignoring result fail could just mean that it already exists,
- // but local database is not synchronized the upload will be
- // tried anyway
- }
-
- // / perform the upload
RemoteOperationResult uploadResult = null;
+
try {
- RemoteOperation operation = new CreateFolderOperation( InstantUploadBroadcastReceiver.INSTANT_UPLOAD_DIR,
+ /// 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());
+ }
+
+ /// create remote folder for instant uploads
+ if (mCurrentUpload.isRemoteFolderToBeCreated()) {
++ RemoteOperation operation = new CreateFolderOperation( FileStorageUtils.getInstantUploadFilePath(this, ""),
+ mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR).getFileId(), // TODO generalize this : INSTANT_UPLOAD_DIR could not be a child of root
+ mStorageManager);
+ operation.execute(mUploadClient); // ignoring result; fail could just mean that it already exists, but local database is not synchronized; the upload will be tried anyway
+ }
+
+
+ /// perform the upload
uploadResult = mCurrentUpload.execute(mUploadClient);
if (uploadResult.isSuccess()) {
saveUploadedFile();
}
-
+
+ } catch (AccountsException e) {
- Log.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);
++ Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);
+ uploadResult = new RemoteOperationResult(e);
+
+ } catch (IOException e) {
- Log.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);
++ Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);
+ uploadResult = new RemoteOperationResult(e);
+
} finally {
- synchronized(mPendingUploads) {
+ synchronized (mPendingUploads) {
mPendingUploads.remove(uploadKey);
+ Log_OC.i(TAG, "Remove CurrentUploadItem from pending upload Item Map.");
}
}
-
- // notify result
+
+ /// notify result
- notifyUploadResult(uploadResult, mCurrentUpload);
+
+ notifyUploadResult(uploadResult, mCurrentUpload);
sendFinalBroadcast(mCurrentUpload, uploadResult);
-
+
}
-
+
}
/**
showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, upload.getFile());
showDetailsIntent.putExtra(FileDetailFragment.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 ERROR; uploader_upload_in_progress_ticker is the target, not a new notification
-
- /* Notification about multiple uploads: pending of update
- mNotification.setLatestEventInfo( getApplicationContext(),
- getString(R.string.uploader_upload_succeeded_ticker),
- String.format(getString(R.string.uploader_upload_succeeded_content_multiple), mSuccessCounter),
- mNotification.contentIntent);
- */
-
+ 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.getFile().getStoragePath());
+ db.close();
+
} else {
- /// fail -> explicit failure notification
+
+ // / 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());
+ Notification finalNotification = new Notification(R.drawable.icon,
+ getString(R.string.uploader_upload_failed_ticker), System.currentTimeMillis());
finalNotification.flags |= Notification.FLAG_AUTO_CANCEL;
-
+ if (uploadResult.getCode() == ResultCode.UNAUTHORIZED) {
+ // let the user update credentials with one click
+ Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
+ updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, upload.getAccount());
+ updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_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);
+ 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);
+ }
+
- String content = null;
- 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));
+ String content = null;
+ 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());
+ 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);
-
+
+ // 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);
-
- /* Notification about multiple uploads failure: pending of update
- finalNotification.setLatestEventInfo( getApplicationContext(),
- getString(R.string.uploader_upload_failed_ticker),
- String.format(getString(R.string.uploader_upload_failed_content_multiple), mSuccessCounter, mTotalFilesToSend),
- finalNotification.contentIntent);
- } */
}
-
+
}
-
-
+
/**
- * Sends a broadcast in order to the interested activities can update their view
+ * 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
+ * @param upload Finished upload operation
+ * @param uploadResult Result of the upload operation
*/
private void sendFinalBroadcast(UploadFileOperation upload, RemoteOperationResult uploadResult) {
Intent end = new Intent(UPLOAD_FINISH_MESSAGE);
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
--import android.util.Log;
public class LocationServiceLauncherReciever extends BroadcastReceiver {
import android.location.LocationProvider;
import android.os.Bundle;
import android.preference.PreferenceManager;
--import android.util.Log;
import android.widget.Toast;
+ import com.owncloud.android.Log_OC;
import com.owncloud.android.R;
public class LocationUpdateService extends IntentService implements
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
- import android.util.Log;
+ import com.owncloud.android.Log_OC;
-import android.util.Log;
-
/**
* AdvancedSSLProtocolSocketFactory allows to create SSL {@link Socket}s with
* a custom SSLContext and an optional Hostname Verifier.
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
- import android.util.Log;
+ import com.owncloud.android.Log_OC;
-import android.util.Log;
-
/**
* @author David A. Velasco
*/
--- /dev/null
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+/* ownCloud Android client application
+ * Copyright (C) 2012 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
- import android.util.Log;
++ * it under the terms of the GNU General Public License version 2,
++ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.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;
+
- Log.d(TAG, "enter BearerScheme.authenticate(Credentials, String, String)");
++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.d(TAG, "enter BearerScheme.authenticate(Credentials, HttpMethod)");
++ 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.d(TAG, "enter BearerAuthScheme.authenticate(BearerCredentials, String)");
++ 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");
+ }
+
+}
--- /dev/null
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+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;
+ }
+
+}
+
import org.apache.http.conn.ssl.X509HostnameVerifier;
import com.owncloud.android.AccountUtils;
+import com.owncloud.android.authentication.AccountAuthenticator;
+ import com.owncloud.android.Log_OC;
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.util.Log;
+import android.os.Bundle;
- import android.util.Log;
public class OwnCloudClientUtils {
/**
* Creates a WebdavClient setup for an ownCloud account
*
- * @param account The ownCloud account
- * @param context The application context
- * @return A WebdavClient object ready to be used
+ * 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.
*/
- public static WebdavClient createOwnCloudClient (Account account, Context context) {
- Log_OC.d(TAG, "Creating WebdavClient associated to " + account.name);
+ public static WebdavClient createOwnCloudClient (Account account, Context appContext) throws OperationCanceledException, AuthenticatorException, IOException {
- //Log.d(TAG, "Creating WebdavClient associated to " + account.name);
++ //Log_OC.d(TAG, "Creating WebdavClient associated to " + account.name);
- Uri uri = Uri.parse(AccountUtils.constructFullURLForAccount(context, account));
- WebdavClient client = createOwnCloudClient(uri, context);
+ Uri uri = Uri.parse(AccountUtils.constructFullURLForAccount(appContext, account));
+ WebdavClient client = createOwnCloudClient(uri, appContext);
+ AccountManager am = AccountManager.get(appContext);
+ if (am.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null) { // TODO avoid a call to getUserData here
+ String accessToken = am.blockingGetAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, false);
+ client.setBearerCredentials(accessToken); // TODO not assume that the access token is a bearer token
- String username = account.name.substring(0, account.name.lastIndexOf('@'));
- String password = AccountManager.get(context).getPassword(account);
- //String password = am.blockingGetAuthToken(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE, true);
-
- client.setCredentials(username, password);
+ } else {
+ String username = account.name.substring(0, account.name.lastIndexOf('@'));
+ //String password = am.getPassword(account);
+ String password = am.blockingGetAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_PASSWORD, false);
+ client.setBasicCredentials(username, password);
+ }
return client;
}
return client;
}
-
-
/**
* Creates a WebdavClient to access a URL and sets the desired parameters for ownCloud client connections.
*
* @return A WebdavClient object ready to be used
*/
public static WebdavClient createOwnCloudClient(Uri uri, Context context) {
- //Log.d(TAG, "Creating WebdavClient for " + uri);
- Log_OC.d(TAG, "Creating WebdavClient for " + uri);
++ //Log_OC.d(TAG, "Creating WebdavClient for " + uri);
//allowSelfsignedCertificates(true);
try {
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 android.util.Log;
import eu.alefzero.webdav.ChunkFromFileChannelRequestEntity;
import eu.alefzero.webdav.WebdavClient;
--- /dev/null
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+/* ownCloud Android client application
+ * Copyright (C) 2012 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
- import android.util.Log;
-
++ * it under the terms of the GNU General Public License version 2,
++ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.operations;
+
+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;
+
- Log.d(TAG, "Create directory " + mRemotePath + ": " + result.getLogMessage());
+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 long mParentDirId;
+ protected DataStorageManager mStorageManager;
+
+ /**
+ * Constructor
+ *
+ * @param remoetPath Full path to the new directory to create in the remote server.
+ * @param parentDirId Local database id for the parent folder.
+ * @param storageManager Reference to the local database corresponding to the account where the file is contained.
+ */
+ public CreateFolderOperation(String remotePath, long parentDirId, DataStorageManager storageManager) {
+ mRemotePath = remotePath;
+ mParentDirId = parentDirId;
+ mStorageManager = storageManager;
+ }
+
+
+ /**
+ * Performs the remove 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()) {
+ // Save new directory in local database
+ OCFile newDir = new OCFile(mRemotePath);
+ newDir.setMimetype("DIR");
+ newDir.setParentId(mParentDirId);
+ mStorageManager.saveFile(newDir);
+ }
+
+ result = new RemoteOperationResult(mkcol.succeeded(), status);
- Log.e(TAG, "Create directory " + mRemotePath + ": " + result.getLogMessage(), e);
++ 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;
+ }
+
+}
import eu.alefzero.webdav.WebdavClient;
import eu.alefzero.webdav.WebdavUtils;
import android.accounts.Account;
--import android.util.Log;
import android.webkit.MimeTypeMap;
/**
--- /dev/null
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+/* ownCloud Android client application
+ * Copyright (C) 2012 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
- import android.util.Log;
++ * it under the terms of the GNU General Public License version 2,
++ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.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 android.content.Context;
+import android.net.ConnectivityManager;
- Log.d(TAG, "Existence check for " + client.getBaseUri() + mPath + " targeting for " + (mSuccessIfAbsent ? " absence " : " existence ") + "finished with HTTP status " + status + (!success?"(FAIL)":""));
+
+/**
+ * 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() + 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);
- Log.e(TAG, "Existence check for " + client.getBaseUri() + mPath + " targeting for " + (mSuccessIfAbsent ? " absence " : " existence ") + ": " + result.getLogMessage(), result.getException());
++ Log_OC.d(TAG, "Existence check for " + client.getBaseUri() + 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() + 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();
+ }
+
+
+}
--- /dev/null
- import android.util.Log;
-
+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;
+
- Log.i(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage());
+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<String, String> mOAuth2ParsedAuthorizationResponse;
+ private Map<String, String> mResultTokenMap;
+
+
+ public OAuth2GetAccessToken(String clientId, String redirectUri, String grantType, String oAuth2AuthorizationResponse) {
+ mClientId = clientId;
+ mRedirectUri = redirectUri;
+ mGrantType = grantType;
+ mOAuth2AuthorizationResponse = oAuth2AuthorizationResponse;
+ mOAuth2ParsedAuthorizationResponse = new HashMap<String, String>();
+ mResultTokenMap = null;
+ }
+
+
+ public Map<String, String> getOauth2AutorizationResponse() {
+ return mOAuth2ParsedAuthorizationResponse;
+ }
+
+ public Map<String, String> 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);
+ }
+
+ } else {
+ client.exhaustResponse(postMethod.getResponseBodyAsStream());
+ result = new RemoteOperationResult(false, status);
+ }
+ }
+
+ } catch (Exception e) {
+ result = new RemoteOperationResult(e);
+
+ } finally {
+ if (postMethod != null)
+ postMethod.releaseConnection(); // let the connection available for other methods
+
+ if (result.isSuccess()) {
- Log.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage(), result.getException());
++ Log_OC.i(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage());
+
+ } else if (result.getException() != null) {
- Log.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + ((mResultTokenMap != null) ? mResultTokenMap.get(OAuth2Constants.KEY_ERROR) : "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.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage());
++ 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.v(TAG, "[" + i + "," + j + "] = " + p);
++ 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<String, String>();
+
+ 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));
+ }
+ }
+
+}
--- /dev/null
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
+/* ownCloud Android client application
+ * Copyright (C) 2012-2013 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
- import android.util.Log;
++ * it under the terms of the GNU General Public License version 2,
++ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.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.AccountUtils;
++import com.owncloud.android.Log_OC;
+import com.owncloud.android.utils.OwnCloudVersion;
+
+import eu.alefzero.webdav.WebdavClient;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Uri;
- Log.i(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage());
+
+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);
+ }
+
+ } 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.e(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage(), mLatestResult.getException());
++ Log_OC.i(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage());
+
+ } else if (mLatestResult.getException() != null) {
- Log.e(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage());
++ Log_OC.e(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage(), mLatestResult.getException());
+
+ } else {
- Log.d(TAG, "establishing secure connection failed, trying non secure connection");
++ 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;
+ }
+
+}
*/
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.authentication.AccountAuthenticator;
+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 android.util.Log;
import eu.alefzero.webdav.WebdavClient;
*/
protected abstract RemoteOperationResult run(WebdavClient client);
- Log.e(TAG, "Error while trying to access to " + mAccount.name, e);
+
+ /**
+ * 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
*/
@Override
public final void run() {
- final RemoteOperationResult result = execute(mClient);
+ 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.e(TAG, "Error while trying to access to " + mAccount.name, new AccountsException("I/O exception while trying to authorize the account", 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.e(TAG, "Error while trying to access to " + mAccount.name, 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) {
+ /// fail due to lack of authorization in an operation performed in foreground
+ AccountManager am = AccountManager.get(mContext);
+ Credentials cred = mClient.getCredentials();
+ if (cred instanceof BearerCredentials) {
+ am.invalidateAuthToken(AccountAuthenticator.ACCOUNT_TYPE, ((BearerCredentials)cred).getAccessToken());
+ } 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
import org.apache.commons.httpclient.HttpStatus;
import org.apache.jackrabbit.webdav.DavException;
-import android.util.Log;
-
+ import com.owncloud.android.Log_OC;
import com.owncloud.android.network.CertificateCombinedException;
-
/**
* The result of a remote operation required to an ownCloud server.
*
* @author David A. Velasco
*/
public class RemoteOperationResult implements Serializable {
-
+
/** Generated - should be refreshed every time the class changes!! */
private static final long serialVersionUID = -7805531062432602444L;
-
- 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, SYNC_CONFLICT, LOCAL_STORAGE_FULL, LOCAL_STORAGE_NOT_MOVED, LOCAL_STORAGE_NOT_COPIED, QUOTA_EXCEEDED
+
+ private static final String TAG = "RemoteOperationResult";
- OAUTH2_ERROR_ACCESS_DENIED
++
+ 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
}
private boolean mSuccess = false;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.jackrabbit.webdav.client.methods.DeleteMethod;
--import android.util.Log;
--
+ import com.owncloud.android.Log_OC;
import com.owncloud.android.datamodel.DataStorageManager;
import com.owncloud.android.datamodel.OCFile;
//import org.apache.jackrabbit.webdav.client.methods.MoveMethod;
import android.accounts.Account;
--import android.util.Log;
+ 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 android.accounts.Account;
import android.content.Context;
import android.content.Intent;
--import android.util.Log;
+ 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 android.accounts.Account;
import android.content.Context;
--import android.util.Log;
+ 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 android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;
--import android.util.Log;
import com.owncloud.android.AccountUtils;
-import com.owncloud.android.authenticator.AccountAuthenticator;
+import com.owncloud.android.authentication.AccountAuthenticator;
+ import com.owncloud.android.Log_OC;
import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
import com.owncloud.android.utils.OwnCloudVersion;
- /* ownCloud Android client application\r
- * Copyright (C) 2011 Bartek Przybylski\r
- * Copyright (C) 2012-2013 ownCloud Inc.\r
- *\r
- * This program is free software: you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 2 of the License, or\r
- * (at your option) any later version.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program. If not, see <http://www.gnu.org/licenses/>.\r
- *\r
- */\r
- \r
- package com.owncloud.android.providers;\r
- \r
- import java.util.HashMap;\r
- \r
- import com.owncloud.android.db.ProviderMeta;\r
- import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;\r
- \r
- \r
- import android.content.ContentProvider;\r
- import android.content.ContentUris;\r
- import android.content.ContentValues;\r
- import android.content.Context;\r
- import android.content.UriMatcher;\r
- import android.database.Cursor;\r
- import android.database.SQLException;\r
- import android.database.sqlite.SQLiteDatabase;\r
- import android.database.sqlite.SQLiteOpenHelper;\r
- import android.database.sqlite.SQLiteQueryBuilder;\r
- import android.net.Uri;\r
- import android.text.TextUtils;\r
- import android.util.Log;\r
- \r
- /**\r
- * The ContentProvider for the ownCloud App.\r
- * \r
- * @author Bartek Przybylski\r
- * \r
- */\r
- public class FileContentProvider extends ContentProvider {\r
- \r
- private DataBaseHelper mDbHelper;\r
- \r
- private static HashMap<String, String> mProjectionMap;\r
- static {\r
- mProjectionMap = new HashMap<String, String>();\r
- mProjectionMap.put(ProviderTableMeta._ID, ProviderTableMeta._ID);\r
- mProjectionMap.put(ProviderTableMeta.FILE_PARENT,\r
- ProviderTableMeta.FILE_PARENT);\r
- mProjectionMap.put(ProviderTableMeta.FILE_PATH,\r
- ProviderTableMeta.FILE_PATH);\r
- mProjectionMap.put(ProviderTableMeta.FILE_NAME,\r
- ProviderTableMeta.FILE_NAME);\r
- mProjectionMap.put(ProviderTableMeta.FILE_CREATION,\r
- ProviderTableMeta.FILE_CREATION);\r
- mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED,\r
- ProviderTableMeta.FILE_MODIFIED);\r
- mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA,\r
- ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA);\r
- mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_LENGTH,\r
- ProviderTableMeta.FILE_CONTENT_LENGTH);\r
- mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_TYPE,\r
- ProviderTableMeta.FILE_CONTENT_TYPE);\r
- mProjectionMap.put(ProviderTableMeta.FILE_STORAGE_PATH,\r
- ProviderTableMeta.FILE_STORAGE_PATH);\r
- mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE,\r
- ProviderTableMeta.FILE_LAST_SYNC_DATE);\r
- mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA,\r
- ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA);\r
- mProjectionMap.put(ProviderTableMeta.FILE_KEEP_IN_SYNC,\r
- ProviderTableMeta.FILE_KEEP_IN_SYNC);\r
- mProjectionMap.put(ProviderTableMeta.FILE_ACCOUNT_OWNER,\r
- ProviderTableMeta.FILE_ACCOUNT_OWNER);\r
- }\r
- \r
- private static final int SINGLE_FILE = 1;\r
- private static final int DIRECTORY = 2;\r
- private static final int ROOT_DIRECTORY = 3;\r
- private static final UriMatcher mUriMatcher;\r
- static {\r
- mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);\r
- mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "/", ROOT_DIRECTORY);\r
- mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "file/", SINGLE_FILE);\r
- mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "file/#", SINGLE_FILE);\r
- mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "dir/#", DIRECTORY);\r
- }\r
- \r
- @Override\r
- public int delete(Uri uri, String where, String[] whereArgs) {\r
- SQLiteDatabase db = mDbHelper.getWritableDatabase();\r
- int count = 0;\r
- switch (mUriMatcher.match(uri)) {\r
- case SINGLE_FILE:\r
- count = db.delete(ProviderTableMeta.DB_NAME,\r
- ProviderTableMeta._ID\r
- + "="\r
- + uri.getPathSegments().get(1)\r
- + (!TextUtils.isEmpty(where) ? " AND (" + where\r
- + ")" : ""), whereArgs);\r
- break;\r
- case ROOT_DIRECTORY:\r
- count = db.delete(ProviderTableMeta.DB_NAME, where, whereArgs);\r
- break;\r
- default:\r
- throw new IllegalArgumentException("Unknown uri: " + uri.toString());\r
- }\r
- getContext().getContentResolver().notifyChange(uri, null);\r
- return count;\r
- }\r
- \r
- @Override\r
- public String getType(Uri uri) {\r
- switch (mUriMatcher.match(uri)) {\r
- case ROOT_DIRECTORY:\r
- return ProviderTableMeta.CONTENT_TYPE;\r
- case SINGLE_FILE:\r
- return ProviderTableMeta.CONTENT_TYPE_ITEM;\r
- default:\r
- throw new IllegalArgumentException("Unknown Uri id."\r
- + uri.toString());\r
- }\r
- }\r
- \r
- @Override\r
- public Uri insert(Uri uri, ContentValues values) {\r
- if (mUriMatcher.match(uri) != SINGLE_FILE &&\r
- mUriMatcher.match(uri) != ROOT_DIRECTORY) {\r
- \r
- throw new IllegalArgumentException("Unknown uri id: " + uri);\r
- }\r
- \r
- SQLiteDatabase db = mDbHelper.getWritableDatabase();\r
- long rowId = db.insert(ProviderTableMeta.DB_NAME, null, values);\r
- if (rowId > 0) {\r
- Uri insertedFileUri = ContentUris.withAppendedId(\r
- ProviderTableMeta.CONTENT_URI_FILE, rowId);\r
- getContext().getContentResolver().notifyChange(insertedFileUri,\r
- null);\r
- return insertedFileUri;\r
- }\r
- throw new SQLException("ERROR " + uri);\r
- }\r
- \r
- @Override\r
- public boolean onCreate() {\r
- mDbHelper = new DataBaseHelper(getContext());\r
- return true;\r
- }\r
- \r
- @Override\r
- public Cursor query(Uri uri, String[] projection, String selection,\r
- String[] selectionArgs, String sortOrder) {\r
- SQLiteQueryBuilder sqlQuery = new SQLiteQueryBuilder();\r
- \r
- sqlQuery.setTables(ProviderTableMeta.DB_NAME);\r
- sqlQuery.setProjectionMap(mProjectionMap);\r
- \r
- switch (mUriMatcher.match(uri)) {\r
- case ROOT_DIRECTORY:\r
- break;\r
- case DIRECTORY:\r
- sqlQuery.appendWhere(ProviderTableMeta.FILE_PARENT + "="\r
- + uri.getPathSegments().get(1));\r
- break;\r
- case SINGLE_FILE:\r
- if (uri.getPathSegments().size() > 1) {\r
- sqlQuery.appendWhere(ProviderTableMeta._ID + "="\r
- + uri.getPathSegments().get(1));\r
- }\r
- break;\r
- default:\r
- throw new IllegalArgumentException("Unknown uri id: " + uri);\r
- }\r
- \r
- String order;\r
- if (TextUtils.isEmpty(sortOrder)) {\r
- order = ProviderTableMeta.DEFAULT_SORT_ORDER;\r
- } else {\r
- order = sortOrder;\r
- }\r
- \r
- SQLiteDatabase db = mDbHelper.getReadableDatabase();\r
- Cursor c = sqlQuery.query(db, projection, selection, selectionArgs,\r
- null, null, order);\r
- \r
- c.setNotificationUri(getContext().getContentResolver(), uri);\r
- \r
- return c;\r
- }\r
- \r
- @Override\r
- public int update(Uri uri, ContentValues values, String selection,\r
- String[] selectionArgs) {\r
- return mDbHelper.getWritableDatabase().update(\r
- ProviderTableMeta.DB_NAME, values, selection, selectionArgs);\r
- }\r
- \r
- class DataBaseHelper extends SQLiteOpenHelper {\r
- \r
- public DataBaseHelper(Context context) {\r
- super(context, ProviderMeta.DB_NAME, null, ProviderMeta.DB_VERSION);\r
- \r
- }\r
- \r
- @Override\r
- public void onCreate(SQLiteDatabase db) {\r
- // files table\r
- Log.i("SQL", "Entering in onCreate");\r
- db.execSQL("CREATE TABLE " + ProviderTableMeta.DB_NAME + "("\r
- + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, "\r
- + ProviderTableMeta.FILE_NAME + " TEXT, "\r
- + ProviderTableMeta.FILE_PATH + " TEXT, "\r
- + ProviderTableMeta.FILE_PARENT + " INTEGER, "\r
- + ProviderTableMeta.FILE_CREATION + " INTEGER, "\r
- + ProviderTableMeta.FILE_MODIFIED + " INTEGER, "\r
- + ProviderTableMeta.FILE_CONTENT_TYPE + " TEXT, "\r
- + ProviderTableMeta.FILE_CONTENT_LENGTH + " INTEGER, "\r
- + ProviderTableMeta.FILE_STORAGE_PATH + " TEXT, "\r
- + ProviderTableMeta.FILE_ACCOUNT_OWNER + " TEXT, "\r
- + ProviderTableMeta.FILE_LAST_SYNC_DATE + " INTEGER, "\r
- + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER, "\r
- + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER, "\r
- + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER );"\r
- );\r
- }\r
- \r
- @Override\r
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\r
- Log.i("SQL", "Entering in onUpgrade");\r
- boolean upgraded = false; \r
- if (oldVersion == 1 && newVersion >= 2) {\r
- Log.i("SQL", "Entering in the #1 ADD in onUpgrade");\r
- db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +\r
- " ADD COLUMN " + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER " +\r
- " DEFAULT 0");\r
- upgraded = true;\r
- }\r
- if (oldVersion < 3 && newVersion >= 3) {\r
- Log.i("SQL", "Entering in the #2 ADD in onUpgrade");\r
- db.beginTransaction();\r
- try {\r
- db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +\r
- " ADD COLUMN " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER " +\r
- " DEFAULT 0");\r
- \r
- // assume there are not local changes pending to upload\r
- db.execSQL("UPDATE " + ProviderTableMeta.DB_NAME + \r
- " SET " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " = " + System.currentTimeMillis() + \r
- " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL");\r
- \r
- upgraded = true;\r
- db.setTransactionSuccessful();\r
- } finally {\r
- db.endTransaction();\r
- }\r
- }\r
- if (oldVersion < 4 && newVersion >= 4) {\r
- Log.i("SQL", "Entering in the #3 ADD in onUpgrade");\r
- db.beginTransaction();\r
- try {\r
- db .execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME +\r
- " ADD COLUMN " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER " +\r
- " DEFAULT 0");\r
- \r
- db.execSQL("UPDATE " + ProviderTableMeta.DB_NAME + \r
- " SET " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " = " + ProviderTableMeta.FILE_MODIFIED + \r
- " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL");\r
- \r
- upgraded = true;\r
- db.setTransactionSuccessful();\r
- } finally {\r
- db.endTransaction();\r
- }\r
- }\r
- if (!upgraded)\r
- Log.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion);\r
- }\r
- \r
- }\r
- \r
- }\r
+ /* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+ package com.owncloud.android.providers;
+
+ import java.util.HashMap;
+
+ import com.owncloud.android.Log_OC;
+ 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;
-import android.util.Log;
+
+ /**
+ * The ContentProvider for the ownCloud App.
+ *
+ * @author Bartek Przybylski
+ *
+ */
+ public class FileContentProvider extends ContentProvider {
+
+ private DataBaseHelper mDbHelper;
+
+ private static HashMap<String, String> mProjectionMap;
+ static {
+ mProjectionMap = new HashMap<String, String>();
+ 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 static final UriMatcher mUriMatcher;
+ static {
+ mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "/", 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());
+ 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();
+ 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);
+ }
+
+ }
+
+ }
- /* ownCloud Android client application\r
- * Copyright (C) 2011 Bartek Przybylski\r
- * Copyright (C) 2012-2013 ownCloud Inc.\r
- *\r
- * This program is free software: you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 2 of the License, or\r
- * (at your option) any later version.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program. If not, see <http://www.gnu.org/licenses/>.\r
- *\r
- */\r
- \r
- package com.owncloud.android.syncadapter;\r
- \r
- import java.io.IOException;\r
- import java.util.ArrayList;\r
- import java.util.HashMap;\r
- import java.util.List;\r
- import java.util.Map;\r
- \r
- import org.apache.jackrabbit.webdav.DavException;\r
- \r
- import com.owncloud.android.R;\r
- import com.owncloud.android.authentication.AuthenticatorActivity;\r
- import com.owncloud.android.datamodel.DataStorageManager;\r
- import com.owncloud.android.datamodel.FileDataStorageManager;\r
- import com.owncloud.android.datamodel.OCFile;\r
- import com.owncloud.android.operations.RemoteOperationResult;\r
- import com.owncloud.android.operations.SynchronizeFolderOperation;\r
- import com.owncloud.android.operations.UpdateOCVersionOperation;\r
- import com.owncloud.android.operations.RemoteOperationResult.ResultCode;\r
- import com.owncloud.android.ui.activity.ErrorsWhileCopyingHandlerActivity;\r
- \r
- import android.accounts.Account;\r
- import android.accounts.AccountsException;\r
- import android.app.Notification;\r
- import android.app.NotificationManager;\r
- import android.app.PendingIntent;\r
- import android.content.ContentProviderClient;\r
- import android.content.ContentResolver;\r
- import android.content.Context;\r
- import android.content.Intent;\r
- import android.content.SyncResult;\r
- import android.os.Bundle;\r
- import android.util.Log;\r
- \r
- /**\r
- * SyncAdapter implementation for syncing sample SyncAdapter contacts to the\r
- * platform ContactOperations provider.\r
- * \r
- * @author Bartek Przybylski\r
- */\r
- public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {\r
- \r
- private final static String TAG = "FileSyncAdapter";\r
- \r
- /** \r
- * Maximum number of failed folder synchronizations that are supported before finishing the synchronization operation\r
- */\r
- private static final int MAX_FAILED_RESULTS = 3; \r
- \r
- private long mCurrentSyncTime;\r
- private boolean mCancellation;\r
- private boolean mIsManualSync;\r
- private int mFailedResultsCounter; \r
- private RemoteOperationResult mLastFailedResult;\r
- private SyncResult mSyncResult;\r
- private int mConflictsFound;\r
- private int mFailsInFavouritesFound;\r
- private Map<String, String> mForgottenLocalFiles;\r
- \r
- \r
- public FileSyncAdapter(Context context, boolean autoInitialize) {\r
- super(context, autoInitialize);\r
- }\r
- \r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public synchronized void onPerformSync(Account account, Bundle extras,\r
- String authority, ContentProviderClient provider,\r
- SyncResult syncResult) {\r
- \r
- mCancellation = false;\r
- mIsManualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);\r
- mFailedResultsCounter = 0;\r
- mLastFailedResult = null;\r
- mConflictsFound = 0;\r
- mFailsInFavouritesFound = 0;\r
- mForgottenLocalFiles = new HashMap<String, String>();\r
- mSyncResult = syncResult;\r
- mSyncResult.fullSyncRequested = false;\r
- mSyncResult.delayUntil = 60*60*24; // sync after 24h\r
- \r
- this.setAccount(account);\r
- this.setContentProvider(provider);\r
- this.setStorageManager(new FileDataStorageManager(account, getContentProvider()));\r
- try {\r
- this.initClientForCurrentAccount();\r
- } catch (IOException e) {\r
- /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again\r
- mSyncResult.tooManyRetries = true;\r
- notifyFailedSynchronization();\r
- return;\r
- } catch (AccountsException e) {\r
- /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again\r
- mSyncResult.tooManyRetries = true;\r
- notifyFailedSynchronization();\r
- return;\r
- }\r
- \r
- Log.d(TAG, "Synchronization of ownCloud account " + account.name + " starting");\r
- sendStickyBroadcast(true, null, null); // message to signal the start of the synchronization to the UI\r
- \r
- try {\r
- updateOCVersion();\r
- mCurrentSyncTime = System.currentTimeMillis();\r
- if (!mCancellation) {\r
- fetchData(OCFile.PATH_SEPARATOR, DataStorageManager.ROOT_PARENT_ID);\r
- \r
- } else {\r
- Log.d(TAG, "Leaving synchronization before any remote request due to cancellation was requested");\r
- }\r
- \r
- \r
- } finally {\r
- // it's important making this although very unexpected errors occur; that's the reason for the finally\r
- \r
- if (mFailedResultsCounter > 0 && mIsManualSync) {\r
- /// don't let the system synchronization manager retries MANUAL synchronizations\r
- // (be careful: "MANUAL" currently includes the synchronization requested when a new account is created and when the user changes the current account)\r
- mSyncResult.tooManyRetries = true;\r
- \r
- /// notify the user about the failure of MANUAL synchronization\r
- notifyFailedSynchronization();\r
- \r
- }\r
- if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) {\r
- notifyFailsInFavourites();\r
- }\r
- if (mForgottenLocalFiles.size() > 0) {\r
- notifyForgottenLocalFiles();\r
- \r
- }\r
- sendStickyBroadcast(false, null, mLastFailedResult); // message to signal the end to the UI\r
- }\r
- \r
- }\r
- \r
- \r
- /**\r
- * Called by system SyncManager when a synchronization is required to be cancelled.\r
- * \r
- * Sets the mCancellation flag to 'true'. THe synchronization will be stopped when before a new folder is fetched. Data of the last folder\r
- * fetched will be still saved in the database. See onPerformSync implementation.\r
- */\r
- @Override\r
- public void onSyncCanceled() {\r
- Log.d(TAG, "Synchronization of " + getAccount().name + " has been requested to cancel");\r
- mCancellation = true;\r
- super.onSyncCanceled();\r
- }\r
- \r
- \r
- /**\r
- * Updates the locally stored version value of the ownCloud server\r
- */\r
- private void updateOCVersion() {\r
- UpdateOCVersionOperation update = new UpdateOCVersionOperation(getAccount(), getContext());\r
- RemoteOperationResult result = update.execute(getClient());\r
- if (!result.isSuccess()) {\r
- mLastFailedResult = result; \r
- }\r
- }\r
- \r
- \r
- \r
- /**\r
- * Synchronize the properties of files and folders contained in a remote folder given by remotePath.\r
- * \r
- * @param remotePath Remote path to the folder to synchronize.\r
- * @param parentId Database Id of the folder to synchronize.\r
- */\r
- private void fetchData(String remotePath, long parentId) {\r
- \r
- if (mFailedResultsCounter > MAX_FAILED_RESULTS || isFinisher(mLastFailedResult))\r
- return;\r
- \r
- // perform folder synchronization\r
- SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation( remotePath, \r
- mCurrentSyncTime, \r
- parentId, \r
- getStorageManager(), \r
- getAccount(), \r
- getContext()\r
- );\r
- RemoteOperationResult result = synchFolderOp.execute(getClient());\r
- \r
- \r
- // synchronized folder -> notice to UI - ALWAYS, although !result.isSuccess\r
- sendStickyBroadcast(true, remotePath, null);\r
- \r
- if (result.isSuccess() || result.getCode() == ResultCode.SYNC_CONFLICT) {\r
- \r
- if (result.getCode() == ResultCode.SYNC_CONFLICT) {\r
- mConflictsFound += synchFolderOp.getConflictsFound();\r
- mFailsInFavouritesFound += synchFolderOp.getFailsInFavouritesFound();\r
- }\r
- if (synchFolderOp.getForgottenLocalFiles().size() > 0) {\r
- mForgottenLocalFiles.putAll(synchFolderOp.getForgottenLocalFiles());\r
- }\r
- // synchronize children folders \r
- List<OCFile> children = synchFolderOp.getChildren();\r
- fetchChildren(children); // beware of the 'hidden' recursion here!\r
- \r
- } else {\r
- if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED) {\r
- mSyncResult.stats.numAuthExceptions++;\r
- \r
- } else if (result.getException() instanceof DavException) {\r
- mSyncResult.stats.numParseExceptions++;\r
- \r
- } else if (result.getException() instanceof IOException) { \r
- mSyncResult.stats.numIoExceptions++;\r
- }\r
- mFailedResultsCounter++;\r
- mLastFailedResult = result;\r
- }\r
- \r
- }\r
- \r
- /**\r
- * Checks if a failed result should terminate the synchronization process immediately, according to\r
- * OUR OWN POLICY\r
- * \r
- * @param failedResult Remote operation result to check.\r
- * @return 'True' if the result should immediately finish the synchronization\r
- */\r
- private boolean isFinisher(RemoteOperationResult failedResult) {\r
- if (failedResult != null) {\r
- RemoteOperationResult.ResultCode code = failedResult.getCode();\r
- return (code.equals(RemoteOperationResult.ResultCode.SSL_ERROR) ||\r
- code.equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) ||\r
- code.equals(RemoteOperationResult.ResultCode.UNAUTHORIZED) ||\r
- code.equals(RemoteOperationResult.ResultCode.BAD_OC_VERSION) ||\r
- code.equals(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED));\r
- }\r
- return false;\r
- }\r
- \r
- /**\r
- * Synchronize data of folders in the list of received files\r
- * \r
- * @param files Files to recursively fetch \r
- */\r
- private void fetchChildren(List<OCFile> files) {\r
- int i;\r
- for (i=0; i < files.size() && !mCancellation; i++) {\r
- OCFile newFile = files.get(i);\r
- if (newFile.isDirectory()) {\r
- fetchData(newFile.getRemotePath(), newFile.getFileId());\r
- }\r
- }\r
- if (mCancellation && i <files.size()) Log.d(TAG, "Leaving synchronization before synchronizing " + files.get(i).getRemotePath() + " because cancelation request");\r
- }\r
- \r
- \r
- /**\r
- * Sends a message to any application component interested in the progress of the synchronization.\r
- * \r
- * @param inProgress 'True' when the synchronization progress is not finished.\r
- * @param dirRemotePath Remote path of a folder that was just synchronized (with or without success)\r
- */\r
- private void sendStickyBroadcast(boolean inProgress, String dirRemotePath, RemoteOperationResult result) {\r
- Intent i = new Intent(FileSyncService.SYNC_MESSAGE);\r
- i.putExtra(FileSyncService.IN_PROGRESS, inProgress);\r
- i.putExtra(FileSyncService.ACCOUNT_NAME, getAccount().name);\r
- if (dirRemotePath != null) {\r
- i.putExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH, dirRemotePath);\r
- }\r
- if (result != null) {\r
- i.putExtra(FileSyncService.SYNC_RESULT, result);\r
- }\r
- getContext().sendStickyBroadcast(i);\r
- }\r
- \r
- \r
- \r
- /**\r
- * Notifies the user about a failed synchronization through the status notification bar \r
- */\r
- private void notifyFailedSynchronization() {\r
- Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_fail_ticker), System.currentTimeMillis());\r
- notification.flags |= Notification.FLAG_AUTO_CANCEL;\r
- boolean needsToUpdateCredentials = (mLastFailedResult != null && mLastFailedResult.getCode() == ResultCode.UNAUTHORIZED);\r
- // TODO put something smart in the contentIntent below for all the possible errors\r
- notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);\r
- if (needsToUpdateCredentials) {\r
- // let the user update credentials with one click\r
- Intent updateAccountCredentials = new Intent(getContext(), AuthenticatorActivity.class);\r
- updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, getAccount());\r
- updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_TOKEN);\r
- updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\r
- updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);\r
- updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);\r
- notification.contentIntent = PendingIntent.getActivity(getContext(), (int)System.currentTimeMillis(), updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT);\r
- notification.setLatestEventInfo(getContext().getApplicationContext(), \r
- getContext().getString(R.string.sync_fail_ticker), \r
- String.format(getContext().getString(R.string.sync_fail_content_unauthorized), getAccount().name), \r
- notification.contentIntent);\r
- } else {\r
- notification.setLatestEventInfo(getContext().getApplicationContext(), \r
- getContext().getString(R.string.sync_fail_ticker), \r
- String.format(getContext().getString(R.string.sync_fail_content), getAccount().name), \r
- notification.contentIntent);\r
- }\r
- ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_ticker, notification);\r
- }\r
- \r
- \r
- /**\r
- * Notifies the user about conflicts and strange fails when trying to synchronize the contents of kept-in-sync files.\r
- * \r
- * By now, we won't consider a failed synchronization.\r
- */\r
- private void notifyFailsInFavourites() {\r
- if (mFailedResultsCounter > 0) {\r
- Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_fail_in_favourites_ticker), System.currentTimeMillis());\r
- notification.flags |= Notification.FLAG_AUTO_CANCEL;\r
- // TODO put something smart in the contentIntent below\r
- notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);\r
- notification.setLatestEventInfo(getContext().getApplicationContext(), \r
- getContext().getString(R.string.sync_fail_in_favourites_ticker), \r
- String.format(getContext().getString(R.string.sync_fail_in_favourites_content), mFailedResultsCounter + mConflictsFound, mConflictsFound), \r
- notification.contentIntent);\r
- ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_in_favourites_ticker, notification);\r
- \r
- } else {\r
- Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_conflicts_in_favourites_ticker), System.currentTimeMillis());\r
- notification.flags |= Notification.FLAG_AUTO_CANCEL;\r
- // TODO put something smart in the contentIntent below\r
- notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);\r
- notification.setLatestEventInfo(getContext().getApplicationContext(), \r
- getContext().getString(R.string.sync_conflicts_in_favourites_ticker), \r
- String.format(getContext().getString(R.string.sync_conflicts_in_favourites_content), mConflictsFound), \r
- notification.contentIntent);\r
- ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_conflicts_in_favourites_ticker, notification);\r
- } \r
- }\r
- \r
- \r
- /**\r
- * Notifies the user about local copies of files out of the ownCloud local directory that were 'forgotten' because \r
- * copying them inside the ownCloud local directory was not possible.\r
- * \r
- * We don't want links to files out of the ownCloud local directory (foreign files) anymore. It's easy to have \r
- * synchronization problems if a local file is linked to more than one remote file.\r
- * \r
- * We won't consider a synchronization as failed when foreign files can not be copied to the ownCloud local directory.\r
- */\r
- private void notifyForgottenLocalFiles() {\r
- Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_foreign_files_forgotten_ticker), System.currentTimeMillis());\r
- notification.flags |= Notification.FLAG_AUTO_CANCEL;\r
- \r
- /// includes a pending intent in the notification showing a more detailed explanation\r
- Intent explanationIntent = new Intent(getContext(), ErrorsWhileCopyingHandlerActivity.class);\r
- explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_ACCOUNT, getAccount());\r
- ArrayList<String> remotePaths = new ArrayList<String>();\r
- ArrayList<String> localPaths = new ArrayList<String>();\r
- remotePaths.addAll(mForgottenLocalFiles.keySet());\r
- localPaths.addAll(mForgottenLocalFiles.values());\r
- explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_LOCAL_PATHS, localPaths);\r
- explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_REMOTE_PATHS, remotePaths); \r
- explanationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);\r
- \r
- notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), explanationIntent, 0);\r
- notification.setLatestEventInfo(getContext().getApplicationContext(), \r
- getContext().getString(R.string.sync_foreign_files_forgotten_ticker), \r
- String.format(getContext().getString(R.string.sync_foreign_files_forgotten_content), mForgottenLocalFiles.size(), getContext().getString(R.string.app_name)), \r
- notification.contentIntent);\r
- ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_foreign_files_forgotten_ticker, notification);\r
- \r
- }\r
- \r
- \r
- }\r
+ /* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+ package com.owncloud.android.syncadapter;
+
+ import java.io.IOException;
-import java.net.UnknownHostException;
+ 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.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;
-import android.util.Log;
+
+ /**
+ * 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<String, String> 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<String, String>();
+ 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 (UnknownHostException e) {
- /// the account is unknown for the Synchronization Manager. unreachable for this context; don't try this again
++ } 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<OCFile> children = synchFolderOp.getChildren();
+ fetchChildren(children); // beware of the 'hidden' recursion here!
+
+ } else {
+ if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED) {
+ 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<OCFile> files) {
+ int i;
+ for (i=0; i < files.size() && !mCancellation; i++) {
+ OCFile newFile = files.get(i);
+ if (newFile.isDirectory()) {
+ fetchData(newFile.getRemotePath(), newFile.getFileId());
+ }
+ }
+ if (mCancellation && i <files.size()) Log_OC.d(TAG, "Leaving synchronization before synchronizing " + files.get(i).getRemotePath() + " because cancelation request");
+ }
+
+
+ /**
+ * Sends a message to any application component interested in the progress of the synchronization.
+ *
+ * @param inProgress 'True' when the synchronization progress is not finished.
+ * @param dirRemotePath Remote path of a folder that was just synchronized (with or without success)
+ */
+ private void sendStickyBroadcast(boolean inProgress, String dirRemotePath, RemoteOperationResult result) {
+ Intent i = new Intent(FileSyncService.SYNC_MESSAGE);
+ i.putExtra(FileSyncService.IN_PROGRESS, inProgress);
+ i.putExtra(FileSyncService.ACCOUNT_NAME, getAccount().name);
+ if (dirRemotePath != null) {
+ i.putExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH, dirRemotePath);
+ }
+ if (result != null) {
+ i.putExtra(FileSyncService.SYNC_RESULT, result);
+ }
+ getContext().sendStickyBroadcast(i);
+ }
+
+
+
+ /**
+ * Notifies the user about a failed synchronization through the status notification bar
+ */
+ private void notifyFailedSynchronization() {
+ Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_fail_ticker), System.currentTimeMillis());
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
- // TODO put something smart in the contentIntent below
++ boolean needsToUpdateCredentials = (mLastFailedResult != null && mLastFailedResult.getCode() == ResultCode.UNAUTHORIZED);
++ // TODO put something smart in the contentIntent below for all the possible errors
+ notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0);
- notification.setLatestEventInfo(getContext().getApplicationContext(),
- getContext().getString(R.string.sync_fail_ticker),
- String.format(getContext().getString(R.string.sync_fail_content), getAccount().name),
- notification.contentIntent);
++ if (needsToUpdateCredentials) {
++ // let the user update credentials with one click
++ Intent updateAccountCredentials = new Intent(getContext(), AuthenticatorActivity.class);
++ updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, getAccount());
++ 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);
++ notification.contentIntent = PendingIntent.getActivity(getContext(), (int)System.currentTimeMillis(), updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT);
++ notification.setLatestEventInfo(getContext().getApplicationContext(),
++ getContext().getString(R.string.sync_fail_ticker),
++ String.format(getContext().getString(R.string.sync_fail_content_unauthorized), getAccount().name),
++ notification.contentIntent);
++ } else {
++ notification.setLatestEventInfo(getContext().getApplicationContext(),
++ getContext().getString(R.string.sync_fail_ticker),
++ String.format(getContext().getString(R.string.sync_fail_content), getAccount().name),
++ notification.contentIntent);
++ }
+ ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_ticker, notification);
+ }
+
+
+ /**
+ * Notifies the user about conflicts and strange fails when trying to synchronize the contents of kept-in-sync files.
+ *
+ * By now, we won't consider a failed synchronization.
+ */
+ private void notifyFailsInFavourites() {
+ if (mFailedResultsCounter > 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<String> remotePaths = new ArrayList<String>();
+ ArrayList<String> localPaths = new ArrayList<String>();
+ 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);
+
+ }
+
+
+ }
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
--import android.util.Log;
import android.view.ContextMenu;
import android.view.View;
import android.view.ViewGroup;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import com.owncloud.android.AccountUtils;
-import com.owncloud.android.authenticator.AccountAuthenticator;
+import com.owncloud.android.authentication.AccountAuthenticator;
+ import com.owncloud.android.Log_OC;
import com.owncloud.android.R;
import android.os.Handler;
import android.support.v4.app.DialogFragment;
import android.text.method.ScrollingMovementMethod;
--import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
--- /dev/null
- * \r
++/* ownCloud Android client application\r
++ * Copyright (C) 2012-2013 ownCloud Inc.\r
++ *\r
++ * This program is free software: you can redistribute it and/or modify\r
++ * it under the terms of the GNU General Public License version 2,\r
++ * as published by the Free Software Foundation.\r
++ *\r
++ * This program is distributed in the hope that it will be useful,\r
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
++ * GNU General Public License for more details.\r
++ *\r
++ * You should have received a copy of the GNU General Public License\r
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.\r
++ *\r
++ */\r
++\r
+ package com.owncloud.android.ui.activity;\r
+ \r
+ import android.app.Activity;\r
+ import android.os.Bundle;\r
+ import android.view.View;\r
+ import android.view.View.OnClickListener;\r
+ import android.widget.Button;\r
+ import android.widget.TextView;\r
+ \r
+ import com.owncloud.android.R;\r
+ \r
+ /**\r
+ * This Activity is used to display a detail message for failed uploads\r
+ * \r
+ * The entry-point for this activity is the 'Failed upload Notification"\r
+ * \r
- * \r
- * This program is free software: you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 3 of the License. (at\r
- * your option) any later version.\r
- * \r
- * This program is distributed in the hope that it will be useful, but\r
- * WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
- * General Public License for more de/\r
+ * @author andomaex / Matthias Baumann\r
+ */\r
+ public class FailedUploadActivity extends Activity {\r
+ \r
+ public static final String MESSAGE = "message";\r
+ \r
+ @Override\r
+ public void onCreate(Bundle savedInstanceState) {\r
+ super.onCreate(savedInstanceState);\r
+ setContentView(R.layout.failed_upload_message_view);\r
+ String message = getIntent().getStringExtra(MESSAGE);\r
+ TextView textView = (TextView) findViewById(R.id.faild_upload_message);\r
+ textView.setText(message);\r
+ Button close_button = (Button) findViewById(R.id.failed_uploadactivity_close_button);\r
+ close_button.setOnClickListener(new OnClickListener() {\r
+ @Override\r
+ public void onClick(View v) {\r
+ finish();\r
+ }\r
+ });\r
+ }\r
+ }\r
- /* ownCloud Android client application\r
- * Copyright (C) 2011 Bartek Przybylski\r
- * Copyright (C) 2012-2013 ownCloud Inc.\r
- *\r
- * This program is free software: you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 2 of the License, or\r
- * (at your option) any later version.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program. If not, see <http://www.gnu.org/licenses/>.\r
- *\r
- */\r
- package com.owncloud.android.ui.activity;\r
- \r
- import android.accounts.Account;\r
- import android.app.Dialog;\r
- import android.app.ProgressDialog;\r
- import android.content.ComponentName;\r
- import android.content.Context;\r
- import android.content.Intent;\r
- import android.content.ServiceConnection;\r
- import android.content.res.Configuration;\r
- import android.os.Bundle;\r
- import android.os.IBinder;\r
- import android.support.v4.app.FragmentTransaction;\r
- import android.util.Log;\r
- \r
- import com.actionbarsherlock.app.ActionBar;\r
- import com.actionbarsherlock.app.SherlockFragmentActivity;\r
- import com.actionbarsherlock.view.MenuItem;\r
- import com.owncloud.android.datamodel.OCFile;\r
- import com.owncloud.android.files.services.FileDownloader;\r
- import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;\r
- import com.owncloud.android.files.services.FileUploader;\r
- import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;\r
- import com.owncloud.android.ui.fragment.FileDetailFragment;\r
- \r
- import com.owncloud.android.R;\r
- \r
- /**\r
- * This activity displays the details of a file like its name, its size and so\r
- * on.\r
- * \r
- * @author Bartek Przybylski\r
- * \r
- */\r
- public class FileDetailActivity extends SherlockFragmentActivity implements FileDetailFragment.ContainerActivity {\r
- \r
- public static final int DIALOG_SHORT_WAIT = 0;\r
- \r
- public static final String TAG = FileDetailActivity.class.getSimpleName();\r
- \r
- private boolean mConfigurationChangedToLandscape = false;\r
- private FileDownloaderBinder mDownloaderBinder = null;\r
- private ServiceConnection mDownloadConnection, mUploadConnection = null;\r
- private FileUploaderBinder mUploaderBinder = null;\r
- \r
- \r
- @Override\r
- protected void onCreate(Bundle savedInstanceState) {\r
- super.onCreate(savedInstanceState);\r
- \r
- // check if configuration changed to large-land ; for a tablet being changed from portrait to landscape when in FileDetailActivity \r
- Configuration conf = getResources().getConfiguration();\r
- mConfigurationChangedToLandscape = (conf.orientation == Configuration.ORIENTATION_LANDSCAPE && \r
- (conf.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE\r
- );\r
- \r
- if (!mConfigurationChangedToLandscape) {\r
- mDownloadConnection = new DetailsServiceConnection();\r
- bindService(new Intent(this, FileDownloader.class), mDownloadConnection, Context.BIND_AUTO_CREATE);\r
- mUploadConnection = new DetailsServiceConnection();\r
- bindService(new Intent(this, FileUploader.class), mUploadConnection, Context.BIND_AUTO_CREATE);\r
- \r
- setContentView(R.layout.file_activity_details);\r
- \r
- ActionBar actionBar = getSupportActionBar();\r
- actionBar.setDisplayHomeAsUpEnabled(true);\r
- \r
- OCFile file = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE);\r
- Account account = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT);\r
- FileDetailFragment mFileDetail = new FileDetailFragment(file, account);\r
- \r
- FragmentTransaction ft = getSupportFragmentManager().beginTransaction();\r
- ft.replace(R.id.fragment, mFileDetail, FileDetailFragment.FTAG);\r
- ft.commit();\r
- \r
- } else {\r
- backToDisplayActivity(); // the 'back' won't be effective until this.onStart() and this.onResume() are completed;\r
- }\r
- \r
- \r
- }\r
- \r
- \r
- /** Defines callbacks for service binding, passed to bindService() */\r
- private class DetailsServiceConnection implements ServiceConnection {\r
- \r
- @Override\r
- public void onServiceConnected(ComponentName component, IBinder service) {\r
- if (component.equals(new ComponentName(FileDetailActivity.this, FileDownloader.class))) {\r
- Log.d(TAG, "Download service connected");\r
- mDownloaderBinder = (FileDownloaderBinder) service;\r
- } else if (component.equals(new ComponentName(FileDetailActivity.this, FileUploader.class))) {\r
- Log.d(TAG, "Upload service connected");\r
- mUploaderBinder = (FileUploaderBinder) service;\r
- } else {\r
- return;\r
- }\r
- FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- if (fragment != null)\r
- fragment.updateFileDetails(false); // let the fragment gets the mDownloadBinder through getDownloadBinder() (see FileDetailFragment#updateFileDetais())\r
- }\r
- \r
- @Override\r
- public void onServiceDisconnected(ComponentName component) {\r
- if (component.equals(new ComponentName(FileDetailActivity.this, FileDownloader.class))) {\r
- Log.d(TAG, "Download service disconnected");\r
- mDownloaderBinder = null;\r
- } else if (component.equals(new ComponentName(FileDetailActivity.this, FileUploader.class))) {\r
- Log.d(TAG, "Upload service disconnected");\r
- mUploaderBinder = null;\r
- }\r
- }\r
- }; \r
- \r
- \r
- @Override\r
- public void onDestroy() {\r
- super.onDestroy();\r
- if (mDownloadConnection != null) {\r
- unbindService(mDownloadConnection);\r
- mDownloadConnection = null;\r
- }\r
- if (mUploadConnection != null) {\r
- unbindService(mUploadConnection);\r
- mUploadConnection = null;\r
- }\r
- }\r
- \r
- \r
- @Override\r
- public boolean onOptionsItemSelected(MenuItem item) {\r
- boolean returnValue = false;\r
- \r
- switch(item.getItemId()){\r
- case android.R.id.home:\r
- backToDisplayActivity();\r
- returnValue = true;\r
- break;\r
- default:\r
- returnValue = super.onOptionsItemSelected(item);\r
- }\r
- \r
- return returnValue;\r
- }\r
- \r
- \r
- \r
- @Override\r
- protected void onResume() {\r
- \r
- super.onResume();\r
- if (!mConfigurationChangedToLandscape) { \r
- FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- fragment.updateFileDetails(false);\r
- }\r
- }\r
- \r
- \r
- private void backToDisplayActivity() {\r
- Intent intent = new Intent(this, FileDisplayActivity.class);\r
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);\r
- intent.putExtra(FileDetailFragment.EXTRA_FILE, getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE));\r
- intent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT));\r
- startActivity(intent);\r
- finish();\r
- }\r
- \r
- \r
- @Override\r
- protected Dialog onCreateDialog(int id) {\r
- Dialog dialog = null;\r
- switch (id) {\r
- case DIALOG_SHORT_WAIT: {\r
- ProgressDialog working_dialog = new ProgressDialog(this);\r
- working_dialog.setMessage(getResources().getString(\r
- R.string.wait_a_moment));\r
- working_dialog.setIndeterminate(true);\r
- working_dialog.setCancelable(false);\r
- dialog = working_dialog;\r
- break;\r
- }\r
- default:\r
- dialog = null;\r
- }\r
- return dialog;\r
- }\r
- \r
- \r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public void onFileStateChanged() {\r
- // nothing to do here!\r
- }\r
- \r
- \r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public FileDownloaderBinder getFileDownloaderBinder() {\r
- return mDownloaderBinder;\r
- }\r
- \r
- \r
- @Override\r
- public FileUploaderBinder getFileUploaderBinder() {\r
- return mUploaderBinder;\r
- }\r
- \r
- }\r
+ /* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+ package com.owncloud.android.ui.activity;
+
+ import android.accounts.Account;
+ import android.app.Dialog;
+ import android.app.ProgressDialog;
+ 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.content.res.Configuration;
+ import android.os.Bundle;
+ import android.os.IBinder;
+ import android.support.v4.app.Fragment;
+ import android.support.v4.app.FragmentTransaction;
-import android.util.Log;
+
+ import com.actionbarsherlock.app.ActionBar;
+ import com.actionbarsherlock.app.SherlockFragmentActivity;
+ import com.actionbarsherlock.view.MenuItem;
+ 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.FileDownloader.FileDownloaderBinder;
+ import com.owncloud.android.files.services.FileUploader;
+ import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+ import com.owncloud.android.ui.fragment.FileDetailFragment;
+ import com.owncloud.android.ui.fragment.FileFragment;
+ import com.owncloud.android.ui.preview.PreviewMediaFragment;
+ import com.owncloud.android.AccountUtils;
+ import com.owncloud.android.Log_OC;
+
+ import com.owncloud.android.R;
+
+ /**
+ * This activity displays the details of a file like its name, its size and so
+ * on.
+ *
+ * @author Bartek Przybylski
+ * @author David A. Velasco
+ */
+ public class FileDetailActivity extends SherlockFragmentActivity implements FileFragment.ContainerActivity {
+
+ public static final int DIALOG_SHORT_WAIT = 0;
+
+ public static final String TAG = FileDetailActivity.class.getSimpleName();
+
+ public static final String EXTRA_MODE = "MODE";
+ public static final int MODE_DETAILS = 0;
+ public static final int MODE_PREVIEW = 1;
+
+ public static final String KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW";
+
+ private boolean mConfigurationChangedToLandscape = false;
+ private FileDownloaderBinder mDownloaderBinder = null;
+ private ServiceConnection mDownloadConnection, mUploadConnection = null;
+ private FileUploaderBinder mUploaderBinder = null;
+ private boolean mWaitingToPreview;
+
+ private OCFile mFile;
+ private Account mAccount;
+
+ private FileDataStorageManager mStorageManager;
+ private DownloadFinishReceiver mDownloadFinishReceiver;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mFile = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE);
+ mAccount = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT);
+ mStorageManager = new FileDataStorageManager(mAccount, getContentResolver());
+
+ // check if configuration changed to large-land ; for a tablet being changed from portrait to landscape when in FileDetailActivity
+ Configuration conf = getResources().getConfiguration();
+ mConfigurationChangedToLandscape = (conf.orientation == Configuration.ORIENTATION_LANDSCAPE &&
+ (conf.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE
+ );
+
+ if (!mConfigurationChangedToLandscape) {
+ setContentView(R.layout.file_activity_details);
+
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+
+ if (savedInstanceState == null) {
+ mWaitingToPreview = false;
+ createChildFragment();
+ } else {
+ mWaitingToPreview = savedInstanceState.getBoolean(KEY_WAITING_TO_PREVIEW);
+ }
+
+ mDownloadConnection = new DetailsServiceConnection();
+ bindService(new Intent(this, FileDownloader.class), mDownloadConnection, Context.BIND_AUTO_CREATE);
+ mUploadConnection = new DetailsServiceConnection();
+ bindService(new Intent(this, FileUploader.class), mUploadConnection, Context.BIND_AUTO_CREATE);
+
+
+ } else {
+ backToDisplayActivity(false); // the 'back' won't be effective until this.onStart() and this.onResume() are completed;
+ }
+
+ }
+
+ /**
+ * Creates the proper fragment depending upon the state of the handled {@link OCFile} and
+ * the requested {@link Intent}.
+ */
+ private void createChildFragment() {
+ int mode = getIntent().getIntExtra(EXTRA_MODE, MODE_PREVIEW);
+
+ Fragment newFragment = null;
+ if (PreviewMediaFragment.canBePreviewed(mFile) && mode == MODE_PREVIEW) {
+ if (mFile.isDown()) {
+ newFragment = new PreviewMediaFragment(mFile, mAccount);
+
+ } else {
+ newFragment = new FileDetailFragment(mFile, mAccount);
+ mWaitingToPreview = true;
+ }
+
+ } else {
+ newFragment = new FileDetailFragment(mFile, mAccount);
+ }
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+ ft.replace(R.id.fragment, newFragment, FileDetailFragment.FTAG);
+ ft.commit();
+ }
+
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(KEY_WAITING_TO_PREVIEW, mWaitingToPreview);
+ }
+
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mDownloadFinishReceiver != null) {
+ unregisterReceiver(mDownloadFinishReceiver);
+ mDownloadFinishReceiver = null;
+ }
+ }
+
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (!mConfigurationChangedToLandscape) {
+ // TODO this is probably unnecessary
+ Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+ if (fragment != null && fragment instanceof FileDetailFragment) {
+ ((FileDetailFragment) fragment).updateFileDetails(false, false);
+ }
+ }
+ // Listen for download messages
+ IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.DOWNLOAD_ADDED_MESSAGE);
+ downloadIntentFilter.addAction(FileDownloader.DOWNLOAD_FINISH_MESSAGE);
+ mDownloadFinishReceiver = new DownloadFinishReceiver();
+ registerReceiver(mDownloadFinishReceiver, downloadIntentFilter);
+ }
+
+
+ /** Defines callbacks for service binding, passed to bindService() */
+ private class DetailsServiceConnection implements ServiceConnection {
+
+ @Override
+ public void onServiceConnected(ComponentName component, IBinder service) {
+
+ if (component.equals(new ComponentName(FileDetailActivity.this, FileDownloader.class))) {
+ Log_OC.d(TAG, "Download service connected");
+ mDownloaderBinder = (FileDownloaderBinder) service;
+ if (mWaitingToPreview) {
+ requestForDownload();
+ }
+
+ } else if (component.equals(new ComponentName(FileDetailActivity.this, FileUploader.class))) {
+ Log_OC.d(TAG, "Upload service connected");
+ mUploaderBinder = (FileUploaderBinder) service;
+ } else {
+ return;
+ }
+
+ Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+ FileDetailFragment detailsFragment = (fragment instanceof FileDetailFragment) ? (FileDetailFragment) fragment : null;
+ if (detailsFragment != null) {
+ detailsFragment.listenForTransferProgress();
+ detailsFragment.updateFileDetails(mWaitingToPreview, false); // let the fragment gets the mDownloadBinder through getDownloadBinder() (see FileDetailFragment#updateFileDetais())
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName component) {
+ if (component.equals(new ComponentName(FileDetailActivity.this, FileDownloader.class))) {
+ Log_OC.d(TAG, "Download service disconnected");
+ mDownloaderBinder = null;
+ } else if (component.equals(new ComponentName(FileDetailActivity.this, FileUploader.class))) {
+ Log_OC.d(TAG, "Upload service disconnected");
+ mUploaderBinder = null;
+ }
+ }
+ };
+
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mDownloadConnection != null) {
+ unbindService(mDownloadConnection);
+ mDownloadConnection = null;
+ }
+ if (mUploadConnection != null) {
+ unbindService(mUploadConnection);
+ mUploadConnection = null;
+ }
+ }
+
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ boolean returnValue = false;
+
+ switch(item.getItemId()){
+ case android.R.id.home:
+ backToDisplayActivity(true);
+ returnValue = true;
+ break;
+ default:
+ returnValue = super.onOptionsItemSelected(item);
+ }
+
+ return returnValue;
+ }
+
+ private void backToDisplayActivity(boolean moveToParent) {
+ Intent intent = new Intent(this, FileDisplayActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ OCFile targetFile = null;
+ if (mFile != null) {
+ targetFile = moveToParent ? mStorageManager.getFileById(mFile.getParentId()) : mFile;
+ }
+ intent.putExtra(FileDetailFragment.EXTRA_FILE, targetFile);
+ intent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, mAccount);
+ startActivity(intent);
+ finish();
+ }
+
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ Dialog dialog = null;
+ 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;
+ }
+ default:
+ dialog = null;
+ }
+ return dialog;
+ }
+
+
+ /**
+ * {@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 showFragmentWithDetails(OCFile file) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.fragment, new FileDetailFragment(file, mAccount), FileDetailFragment.FTAG);
+ transaction.commit();
+ }
+
+
+ private void requestForDownload() {
+ if (!mDownloaderBinder.isDownloading(mAccount, mFile)) {
+ Intent i = new Intent(this, FileDownloader.class);
+ i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
+ i.putExtra(FileDownloader.EXTRA_FILE, mFile);
+ startService(i);
+ }
+ }
+
+
+ /**
+ * 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 file.
+ */
+ 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 samePath = (mFile != null && mFile.getRemotePath().equals(downloadedRemotePath));
+
+ if (sameAccount && samePath) {
+ updateChildFragment(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false));
+ }
+
+ removeStickyBroadcast(intent);
+ }
+
+ private boolean isSameAccount(Context context, Intent intent) {
+ String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);
+ return (accountName != null && accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name));
+ }
+ }
+
+
+ public void updateChildFragment(String downloadEvent, String downloadedRemotePath, boolean success) {
+ Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+ if (fragment != null && fragment instanceof FileDetailFragment) {
+ FileDetailFragment detailsFragment = (FileDetailFragment) fragment;
+ OCFile fileInFragment = detailsFragment.getFile();
+ if (fileInFragment != null && !downloadedRemotePath.equals(fileInFragment.getRemotePath())) {
+ // this never should happen; fileInFragment should be always equals to mFile, that was compared to downloadedRemotePath in DownloadReceiver
+ mWaitingToPreview = false;
+
+ } else if (downloadEvent.equals(FileDownloader.DOWNLOAD_ADDED_MESSAGE)) {
+ // grants that the progress bar is updated
+ detailsFragment.listenForTransferProgress();
+ detailsFragment.updateFileDetails(true, false);
+
+ } else if (downloadEvent.equals(FileDownloader.DOWNLOAD_FINISH_MESSAGE)) {
+ // refresh the details fragment
+ if (success && mWaitingToPreview) {
+ mFile = mStorageManager.getFileById(mFile.getFileId()); // update the file from database, for the local storage path
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.fragment, new PreviewMediaFragment(mFile, mAccount), FileDetailFragment.FTAG);
+ transaction.commit();
+ mWaitingToPreview = false;
+
+ } else {
+ detailsFragment.updateFileDetails(false, (success));
+ // TODO error message if !success ¿?
+ }
+ }
+ } // TODO else if (fragment != null && fragment )
+
+ }
+
+ }
- /* ownCloud Android client application\r
- * Copyright (C) 2011 Bartek Przybylski\r
- * Copyright (C) 2012-2013 ownCloud Inc.\r
- *\r
- * This program is free software: you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 2 of the License, or\r
- * (at your option) any later version.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program. If not, see <http://www.gnu.org/licenses/>.\r
- *\r
- */\r
- \r
- package com.owncloud.android.ui.activity;\r
- \r
- import java.io.File;\r
- \r
- import android.accounts.Account;\r
- import android.app.AlertDialog;\r
- import android.app.ProgressDialog;\r
- import android.app.AlertDialog.Builder;\r
- import android.app.Dialog;\r
- import android.content.BroadcastReceiver;\r
- import android.content.ComponentName;\r
- import android.content.ContentResolver;\r
- import android.content.Context;\r
- import android.content.DialogInterface;\r
- import android.content.DialogInterface.OnClickListener;\r
- import android.content.Intent;\r
- import android.content.IntentFilter;\r
- import android.content.ServiceConnection;\r
- import android.content.SharedPreferences;\r
- import android.content.SharedPreferences.Editor;\r
- import android.content.pm.PackageInfo;\r
- import android.content.pm.PackageManager.NameNotFoundException;\r
- import android.content.res.Resources.NotFoundException;\r
- import android.database.Cursor;\r
- import android.graphics.Bitmap;\r
- import android.graphics.drawable.BitmapDrawable;\r
- import android.net.Uri;\r
- import android.os.Bundle;\r
- import android.os.Handler;\r
- import android.os.IBinder;\r
- import android.preference.PreferenceManager;\r
- import android.provider.MediaStore;\r
- import android.support.v4.app.FragmentTransaction;\r
- import android.util.Log;\r
- import android.view.View;\r
- import android.view.ViewGroup;\r
- import android.widget.ArrayAdapter;\r
- import android.widget.EditText;\r
- import android.widget.TextView;\r
- import android.widget.Toast;\r
- \r
- import com.actionbarsherlock.app.ActionBar;\r
- import com.actionbarsherlock.app.ActionBar.OnNavigationListener;\r
- import com.actionbarsherlock.app.SherlockFragmentActivity;\r
- import com.actionbarsherlock.view.Menu;\r
- import com.actionbarsherlock.view.MenuInflater;\r
- import com.actionbarsherlock.view.MenuItem;\r
- import com.actionbarsherlock.view.Window;\r
- import com.owncloud.android.AccountUtils;\r
- import com.owncloud.android.authentication.AccountAuthenticator;\r
- import com.owncloud.android.datamodel.DataStorageManager;\r
- import com.owncloud.android.datamodel.FileDataStorageManager;\r
- import com.owncloud.android.datamodel.OCFile;\r
- import com.owncloud.android.files.services.FileDownloader;\r
- import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;\r
- import com.owncloud.android.files.services.FileObserverService;\r
- import com.owncloud.android.files.services.FileUploader;\r
- import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;\r
- import com.owncloud.android.operations.CreateFolderOperation;\r
- import com.owncloud.android.operations.OnRemoteOperationListener;\r
- import com.owncloud.android.operations.RemoteOperation;\r
- import com.owncloud.android.operations.RemoteOperationResult;\r
- import com.owncloud.android.operations.RemoveFileOperation;\r
- import com.owncloud.android.operations.RenameFileOperation;\r
- import com.owncloud.android.operations.SynchronizeFileOperation;\r
- import com.owncloud.android.operations.RemoteOperationResult.ResultCode;\r
- import com.owncloud.android.syncadapter.FileSyncService;\r
- import com.owncloud.android.ui.dialog.ChangelogDialog;\r
- import com.owncloud.android.ui.dialog.SslValidatorDialog;\r
- import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener;\r
- import com.owncloud.android.ui.fragment.FileDetailFragment;\r
- import com.owncloud.android.ui.fragment.OCFileListFragment;\r
- \r
- import com.owncloud.android.R;\r
- \r
- /**\r
- * Displays, what files the user has available in his ownCloud.\r
- * \r
- * @author Bartek Przybylski\r
- * \r
- */\r
- \r
- public class FileDisplayActivity extends SherlockFragmentActivity implements\r
- OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener, OnSslValidatorListener, OnRemoteOperationListener {\r
- \r
- private ArrayAdapter<String> mDirectories;\r
- private OCFile mCurrentDir = null;\r
- private OCFile mCurrentFile = null;\r
- \r
- private DataStorageManager mStorageManager;\r
- private SyncBroadcastReceiver mSyncBroadcastReceiver;\r
- private UploadFinishReceiver mUploadFinishReceiver;\r
- private DownloadFinishReceiver mDownloadFinishReceiver;\r
- private FileDownloaderBinder mDownloaderBinder = null;\r
- private FileUploaderBinder mUploaderBinder = null;\r
- private ServiceConnection mDownloadConnection = null, mUploadConnection = null;\r
- private RemoteOperationResult mLastSslUntrustedServerResult = null;\r
- \r
- private OCFileListFragment mFileList;\r
- \r
- private boolean mDualPane;\r
- private Handler mHandler;\r
- \r
- private static final int DIALOG_SETUP_ACCOUNT = 0;\r
- private static final int DIALOG_CREATE_DIR = 1;\r
- private static final int DIALOG_ABOUT_APP = 2;\r
- public static final int DIALOG_SHORT_WAIT = 3;\r
- private static final int DIALOG_CHOOSE_UPLOAD_SOURCE = 4;\r
- private static final int DIALOG_SSL_VALIDATOR = 5;\r
- private static final int DIALOG_CERT_NOT_SAVED = 6;\r
- private static final String DIALOG_CHANGELOG_TAG = "DIALOG_CHANGELOG";\r
- \r
- \r
- private static final int ACTION_SELECT_CONTENT_FROM_APPS = 1;\r
- private static final int ACTION_SELECT_MULTIPLE_FILES = 2;\r
- \r
- private static final String TAG = "FileDisplayActivity";\r
- \r
- private static int[] mMenuIdentifiersToPatch = {R.id.about_app};\r
- \r
- @Override\r
- public void onCreate(Bundle savedInstanceState) {\r
- Log.d(getClass().toString(), "onCreate() start");\r
- super.onCreate(savedInstanceState);\r
- \r
- mHandler = new Handler();\r
- \r
- /// Load of parameters from received intent\r
- Account account = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT);\r
- if (account != null && AccountUtils.setCurrentOwnCloudAccount(this, account.name)) {\r
- mCurrentDir = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE); \r
- }\r
- \r
- /// Load of saved instance state: keep this always before initDataFromCurrentAccount()\r
- if(savedInstanceState != null) {\r
- // TODO - test if savedInstanceState should take precedence over file in the intent ALWAYS (now), NEVER, or SOME TIMES\r
- mCurrentDir = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_FILE);\r
- }\r
- \r
- if (!AccountUtils.accountsAreSetup(this)) {\r
- /// no account available: FORCE ACCOUNT CREATION\r
- mStorageManager = null;\r
- createFirstAccount();\r
- \r
- } else { /// at least an account is available\r
- \r
- initDataFromCurrentAccount(); // it checks mCurrentDir and mCurrentFile with the current account\r
- \r
- }\r
- \r
- mUploadConnection = new ListServiceConnection(); \r
- mDownloadConnection = new ListServiceConnection();\r
- bindService(new Intent(this, FileUploader.class), mUploadConnection, Context.BIND_AUTO_CREATE);\r
- bindService(new Intent(this, FileDownloader.class), mDownloadConnection, Context.BIND_AUTO_CREATE);\r
- \r
- // PIN CODE request ; best location is to decide, let's try this first\r
- if (getIntent().getAction() != null && getIntent().getAction().equals(Intent.ACTION_MAIN) && savedInstanceState == null) {\r
- requestPinCode();\r
- }\r
- \r
- // file observer\r
- Intent observer_intent = new Intent(this, FileObserverService.class);\r
- observer_intent.putExtra(FileObserverService.KEY_FILE_CMD, FileObserverService.CMD_INIT_OBSERVED_LIST);\r
- startService(observer_intent);\r
- \r
- \r
- /// USER INTERFACE\r
- requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);\r
- \r
- // Drop-down navigation \r
- mDirectories = new CustomArrayAdapter<String>(this, R.layout.sherlock_spinner_dropdown_item);\r
- OCFile currFile = mCurrentDir;\r
- while(mStorageManager != null && currFile != null && currFile.getFileName() != OCFile.PATH_SEPARATOR) {\r
- mDirectories.add(currFile.getFileName());\r
- currFile = mStorageManager.getFileById(currFile.getParentId());\r
- }\r
- mDirectories.add(OCFile.PATH_SEPARATOR);\r
- \r
- // Inflate and set the layout view\r
- setContentView(R.layout.files); \r
- mFileList = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);\r
- mDualPane = (findViewById(R.id.file_details_container) != null);\r
- if (mDualPane) {\r
- initFileDetailsInDualPane();\r
- }\r
- \r
- // Action bar setup\r
- ActionBar actionBar = getSupportActionBar();\r
- actionBar.setHomeButtonEnabled(true); // mandatory since Android ICS, according to the official documentation\r
- actionBar.setDisplayHomeAsUpEnabled(mCurrentDir != null && mCurrentDir.getParentId() != 0);\r
- actionBar.setDisplayShowTitleEnabled(false);\r
- actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);\r
- actionBar.setListNavigationCallbacks(mDirectories, this);\r
- setSupportProgressBarIndeterminateVisibility(false); // always AFTER setContentView(...) ; to workaround bug in its implementation\r
- \r
- \r
- // show changelog, if needed\r
- //showChangeLog();\r
- \r
- Log.d(getClass().toString(), "onCreate() end");\r
- }\r
- \r
- \r
- /**\r
- * Shows a dialog with the change log of the current version after each app update\r
- * \r
- * TODO make it permanent; by now, only to advice the workaround app for 4.1.x\r
- */\r
- private void showChangeLog() {\r
- if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.JELLY_BEAN) {\r
- final String KEY_VERSION = "version";\r
- SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());\r
- int currentVersionNumber = 0;\r
- int savedVersionNumber = sharedPref.getInt(KEY_VERSION, 0);\r
- try {\r
- PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);\r
- currentVersionNumber = pi.versionCode;\r
- } catch (Exception e) {}\r
- \r
- if (currentVersionNumber > savedVersionNumber) {\r
- ChangelogDialog.newInstance(true).show(getSupportFragmentManager(), DIALOG_CHANGELOG_TAG);\r
- Editor editor = sharedPref.edit();\r
- editor.putInt(KEY_VERSION, currentVersionNumber);\r
- editor.commit();\r
- }\r
- }\r
- }\r
- \r
- \r
- /**\r
- * Launches the account creation activity. To use when no ownCloud account is available\r
- */\r
- private void createFirstAccount() {\r
- Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT);\r
- intent.putExtra(android.provider.Settings.EXTRA_AUTHORITIES, new String[] { AccountAuthenticator.AUTHORITY });\r
- startActivity(intent); // the new activity won't be created until this.onStart() and this.onResume() are finished;\r
- }\r
- \r
- \r
- /**\r
- * Load of state dependent of the existence of an ownCloud account\r
- */\r
- private void initDataFromCurrentAccount() {\r
- /// Storage manager initialization - access to local database\r
- mStorageManager = new FileDataStorageManager(\r
- AccountUtils.getCurrentOwnCloudAccount(this),\r
- getContentResolver());\r
- \r
- /// Check if mCurrentDir is a directory\r
- if(mCurrentDir != null && !mCurrentDir.isDirectory()) {\r
- mCurrentFile = mCurrentDir;\r
- mCurrentDir = mStorageManager.getFileById(mCurrentDir.getParentId());\r
- }\r
- \r
- /// Check if mCurrentDir and mCurrentFile are in the current account, and update them\r
- if (mCurrentDir != null) {\r
- mCurrentDir = mStorageManager.getFileByPath(mCurrentDir.getRemotePath()); // mCurrentDir == null if it is not in the current account\r
- }\r
- if (mCurrentFile != null) {\r
- if (mCurrentFile.fileExists()) {\r
- mCurrentFile = mStorageManager.getFileByPath(mCurrentFile.getRemotePath()); // mCurrentFile == null if it is not in the current account\r
- } // else : keep mCurrentFile with the received value; this is currently the case of an upload in progress, when the user presses the status notification in a landscape tablet\r
- }\r
- \r
- /// Default to root if mCurrentDir was not found\r
- if (mCurrentDir == null) {\r
- mCurrentDir = mStorageManager.getFileByPath("/"); // will be NULL if the database was never synchronized\r
- }\r
- }\r
- \r
- \r
- private void initFileDetailsInDualPane() {\r
- if (mDualPane && getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG) == null) {\r
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
- if (mCurrentFile != null) {\r
- transaction.replace(R.id.file_details_container, new FileDetailFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); // empty FileDetailFragment\r
- mCurrentFile = null;\r
- } else {\r
- transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment\r
- }\r
- transaction.commit();\r
- }\r
- }\r
- \r
- \r
- @Override\r
- public void onDestroy() {\r
- super.onDestroy();\r
- if (mDownloadConnection != null)\r
- unbindService(mDownloadConnection);\r
- if (mUploadConnection != null)\r
- unbindService(mUploadConnection);\r
- }\r
- \r
- \r
- @Override\r
- public boolean onCreateOptionsMenu(Menu menu) {\r
- MenuInflater inflater = getSherlock().getMenuInflater();\r
- inflater.inflate(R.menu.menu, menu);\r
- \r
- patchHiddenAccents(menu);\r
- \r
- return true;\r
- }\r
- \r
- /**\r
- * Workaround for this: <a href="http://code.google.com/p/android/issues/detail?id=3974">http://code.google.com/p/android/issues/detail?id=3974</a> \r
- * \r
- * @param menu Menu to patch\r
- */\r
- private void patchHiddenAccents(Menu menu) {\r
- for (int i = 0; i < mMenuIdentifiersToPatch.length ; i++) {\r
- MenuItem aboutItem = menu.findItem(mMenuIdentifiersToPatch[i]);\r
- if (aboutItem != null && aboutItem.getIcon() instanceof BitmapDrawable) {\r
- // Clip off the bottom three (density independent) pixels of transparent padding\r
- Bitmap original = ((BitmapDrawable) aboutItem.getIcon()).getBitmap();\r
- float scale = getResources().getDisplayMetrics().density;\r
- int clippedHeight = (int) (original.getHeight() - (3 * scale));\r
- Bitmap scaled = Bitmap.createBitmap(original, 0, 0, original.getWidth(), clippedHeight);\r
- aboutItem.setIcon(new BitmapDrawable(getResources(), scaled));\r
- }\r
- }\r
- }\r
- \r
- \r
- @Override\r
- public boolean onOptionsItemSelected(MenuItem item) {\r
- boolean retval = true;\r
- switch (item.getItemId()) {\r
- case R.id.createDirectoryItem: {\r
- showDialog(DIALOG_CREATE_DIR);\r
- break;\r
- }\r
- case R.id.startSync: {\r
- startSynchronization();\r
- break;\r
- }\r
- case R.id.action_upload: {\r
- showDialog(DIALOG_CHOOSE_UPLOAD_SOURCE);\r
- break;\r
- }\r
- case R.id.action_settings: {\r
- Intent settingsIntent = new Intent(this, Preferences.class);\r
- startActivity(settingsIntent);\r
- break;\r
- }\r
- case R.id.about_app : {\r
- showDialog(DIALOG_ABOUT_APP);\r
- break;\r
- }\r
- case android.R.id.home: {\r
- if(mCurrentDir != null && mCurrentDir.getParentId() != 0){\r
- onBackPressed(); \r
- }\r
- break;\r
- }\r
- default:\r
- retval = super.onOptionsItemSelected(item);\r
- }\r
- return retval;\r
- }\r
- \r
- private void startSynchronization() {\r
- ContentResolver.cancelSync(null, AccountAuthenticator.AUTHORITY); // cancel the current synchronizations of any ownCloud account\r
- Bundle bundle = new Bundle();\r
- bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);\r
- ContentResolver.requestSync(\r
- AccountUtils.getCurrentOwnCloudAccount(this),\r
- AccountAuthenticator.AUTHORITY, bundle);\r
- }\r
- \r
- \r
- @Override\r
- public boolean onNavigationItemSelected(int itemPosition, long itemId) {\r
- int i = itemPosition;\r
- while (i-- != 0) {\r
- onBackPressed();\r
- }\r
- // the next operation triggers a new call to this method, but it's necessary to \r
- // ensure that the name exposed in the action bar is the current directory when the \r
- // user selected it in the navigation list\r
- if (itemPosition != 0)\r
- getSupportActionBar().setSelectedNavigationItem(0);\r
- return true;\r
- }\r
- \r
- /**\r
- * Called, when the user selected something for uploading\r
- */\r
- public void onActivityResult(int requestCode, int resultCode, Intent data) {\r
- \r
- if (requestCode == ACTION_SELECT_CONTENT_FROM_APPS && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {\r
- requestSimpleUpload(data, resultCode);\r
- \r
- } else if (requestCode == ACTION_SELECT_MULTIPLE_FILES && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {\r
- requestMultipleUpload(data, resultCode);\r
- \r
- }\r
- }\r
- \r
- private void requestMultipleUpload(Intent data, int resultCode) {\r
- String[] filePaths = data.getStringArrayExtra(UploadFilesActivity.EXTRA_CHOSEN_FILES);\r
- if (filePaths != null) {\r
- String[] remotePaths = new String[filePaths.length];\r
- String remotePathBase = "";\r
- for (int j = mDirectories.getCount() - 2; j >= 0; --j) {\r
- remotePathBase += OCFile.PATH_SEPARATOR + mDirectories.getItem(j);\r
- }\r
- if (!remotePathBase.endsWith(OCFile.PATH_SEPARATOR))\r
- remotePathBase += OCFile.PATH_SEPARATOR;\r
- for (int j = 0; j< remotePaths.length; j++) {\r
- remotePaths[j] = remotePathBase + (new File(filePaths[j])).getName();\r
- }\r
- \r
- Intent i = new Intent(this, FileUploader.class);\r
- i.putExtra(FileUploader.KEY_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));\r
- i.putExtra(FileUploader.KEY_LOCAL_FILE, filePaths);\r
- i.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths);\r
- i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES);\r
- if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)\r
- i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);\r
- startService(i);\r
- \r
- } else {\r
- Log.d("FileDisplay", "User clicked on 'Update' with no selection");\r
- Toast t = Toast.makeText(this, getString(R.string.filedisplay_no_file_selected), Toast.LENGTH_LONG);\r
- t.show();\r
- return;\r
- }\r
- }\r
- \r
- \r
- private void requestSimpleUpload(Intent data, int resultCode) {\r
- String filepath = null;\r
- try {\r
- Uri selectedImageUri = data.getData();\r
- \r
- String filemanagerstring = selectedImageUri.getPath();\r
- String selectedImagePath = getPath(selectedImageUri);\r
- \r
- if (selectedImagePath != null)\r
- filepath = selectedImagePath;\r
- else\r
- filepath = filemanagerstring;\r
- \r
- } catch (Exception e) {\r
- Log.e("FileDisplay", "Unexpected exception when trying to read the result of Intent.ACTION_GET_CONTENT", e);\r
- e.printStackTrace();\r
- \r
- } finally {\r
- if (filepath == null) {\r
- Log.e("FileDisplay", "Couldnt resolve path to file");\r
- Toast t = Toast.makeText(this, getString(R.string.filedisplay_unexpected_bad_get_content), Toast.LENGTH_LONG);\r
- t.show();\r
- return;\r
- }\r
- }\r
- \r
- Intent i = new Intent(this, FileUploader.class);\r
- i.putExtra(FileUploader.KEY_ACCOUNT,\r
- AccountUtils.getCurrentOwnCloudAccount(this));\r
- String remotepath = new String();\r
- for (int j = mDirectories.getCount() - 2; j >= 0; --j) {\r
- remotepath += OCFile.PATH_SEPARATOR + mDirectories.getItem(j);\r
- }\r
- if (!remotepath.endsWith(OCFile.PATH_SEPARATOR))\r
- remotepath += OCFile.PATH_SEPARATOR;\r
- remotepath += new File(filepath).getName();\r
- \r
- i.putExtra(FileUploader.KEY_LOCAL_FILE, filepath);\r
- i.putExtra(FileUploader.KEY_REMOTE_FILE, remotepath);\r
- i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE);\r
- if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)\r
- i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE);\r
- startService(i);\r
- }\r
- \r
- \r
- @Override\r
- public void onBackPressed() {\r
- if (mDirectories.getCount() <= 1) {\r
- finish();\r
- return;\r
- }\r
- popDirname();\r
- mFileList.onNavigateUp();\r
- mCurrentDir = mFileList.getCurrentFile();\r
- \r
- if (mDualPane) {\r
- // Resets the FileDetailsFragment on Tablets so that it always displays\r
- FileDetailFragment fileDetails = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- if (fileDetails != null && !fileDetails.isEmpty()) {\r
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
- transaction.remove(fileDetails);\r
- transaction.add(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG);\r
- transaction.commit();\r
- }\r
- }\r
- \r
- if(mCurrentDir.getParentId() == 0){\r
- ActionBar actionBar = getSupportActionBar(); \r
- actionBar.setDisplayHomeAsUpEnabled(false);\r
- } \r
- }\r
- \r
- @Override\r
- protected void onSaveInstanceState(Bundle outState) {\r
- // responsibility of restore is preferred in onCreate() before than in onRestoreInstanceState when there are Fragments involved\r
- Log.d(getClass().toString(), "onSaveInstanceState() start");\r
- super.onSaveInstanceState(outState);\r
- outState.putParcelable(FileDetailFragment.EXTRA_FILE, mCurrentDir);\r
- if (mDualPane) {\r
- FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- if (fragment != null) {\r
- OCFile file = fragment.getDisplayedFile();\r
- if (file != null) {\r
- outState.putParcelable(FileDetailFragment.EXTRA_FILE, file);\r
- }\r
- }\r
- }\r
- Log.d(getClass().toString(), "onSaveInstanceState() end");\r
- }\r
- \r
- @Override\r
- protected void onResume() {\r
- Log.d(getClass().toString(), "onResume() start");\r
- super.onResume();\r
- \r
- if (AccountUtils.accountsAreSetup(this)) {\r
- \r
- if (mStorageManager == null) {\r
- // this is necessary for handling the come back to FileDisplayActivity when the first ownCloud account is created \r
- initDataFromCurrentAccount();\r
- if (mDualPane) {\r
- initFileDetailsInDualPane();\r
- }\r
- }\r
- \r
- // Listen for sync messages\r
- IntentFilter syncIntentFilter = new IntentFilter(FileSyncService.SYNC_MESSAGE);\r
- mSyncBroadcastReceiver = new SyncBroadcastReceiver();\r
- registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);\r
- \r
- // Listen for upload messages\r
- IntentFilter uploadIntentFilter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE);\r
- mUploadFinishReceiver = new UploadFinishReceiver();\r
- registerReceiver(mUploadFinishReceiver, uploadIntentFilter);\r
- \r
- // Listen for download messages\r
- IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.DOWNLOAD_FINISH_MESSAGE);\r
- mDownloadFinishReceiver = new DownloadFinishReceiver();\r
- registerReceiver(mDownloadFinishReceiver, downloadIntentFilter);\r
- \r
- // List current directory\r
- mFileList.listDirectory(mCurrentDir); // TODO we should find the way to avoid the need of this (maybe it's not necessary yet; to check)\r
- \r
- } else {\r
- \r
- mStorageManager = null; // an invalid object will be there if all the ownCloud accounts are removed\r
- showDialog(DIALOG_SETUP_ACCOUNT);\r
- \r
- }\r
- Log.d(getClass().toString(), "onResume() end");\r
- }\r
- \r
- \r
- @Override\r
- protected void onPause() {\r
- Log.d(getClass().toString(), "onPause() start");\r
- super.onPause();\r
- if (mSyncBroadcastReceiver != null) {\r
- unregisterReceiver(mSyncBroadcastReceiver);\r
- mSyncBroadcastReceiver = null;\r
- }\r
- if (mUploadFinishReceiver != null) {\r
- unregisterReceiver(mUploadFinishReceiver);\r
- mUploadFinishReceiver = null;\r
- }\r
- if (mDownloadFinishReceiver != null) {\r
- unregisterReceiver(mDownloadFinishReceiver);\r
- mDownloadFinishReceiver = null;\r
- }\r
- if (!AccountUtils.accountsAreSetup(this)) {\r
- dismissDialog(DIALOG_SETUP_ACCOUNT);\r
- }\r
- \r
- Log.d(getClass().toString(), "onPause() end");\r
- }\r
- \r
- \r
- @Override\r
- protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {\r
- if (id == DIALOG_SSL_VALIDATOR && mLastSslUntrustedServerResult != null) {\r
- ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult);\r
- }\r
- }\r
- \r
- \r
- @Override\r
- protected Dialog onCreateDialog(int id) {\r
- Dialog dialog = null;\r
- AlertDialog.Builder builder;\r
- switch (id) {\r
- case DIALOG_SETUP_ACCOUNT: {\r
- builder = new AlertDialog.Builder(this);\r
- builder.setTitle(R.string.main_tit_accsetup);\r
- builder.setMessage(R.string.main_wrn_accsetup);\r
- builder.setCancelable(false);\r
- builder.setPositiveButton(android.R.string.ok, new OnClickListener() {\r
- public void onClick(DialogInterface dialog, int which) {\r
- createFirstAccount();\r
- dialog.dismiss();\r
- }\r
- });\r
- String message = String.format(getString(R.string.common_exit), getString(R.string.app_name));\r
- builder.setNegativeButton(message, new OnClickListener() {\r
- public void onClick(DialogInterface dialog, int which) {\r
- dialog.dismiss();\r
- finish();\r
- }\r
- });\r
- //builder.setNegativeButton(android.R.string.cancel, this);\r
- dialog = builder.create();\r
- break;\r
- }\r
- case DIALOG_ABOUT_APP: {\r
- builder = new AlertDialog.Builder(this);\r
- builder.setTitle(getString(R.string.about_title));\r
- PackageInfo pkg;\r
- try {\r
- pkg = getPackageManager().getPackageInfo(getPackageName(), 0);\r
- builder.setMessage(String.format(getString(R.string.about_message), getString(R.string.app_name), pkg.versionName));\r
- builder.setIcon(android.R.drawable.ic_menu_info_details);\r
- dialog = builder.create();\r
- } catch (NameNotFoundException e) {\r
- builder = null;\r
- dialog = null;\r
- Log.e(TAG, "Error while showing about dialog", e);\r
- }\r
- break;\r
- }\r
- case DIALOG_CREATE_DIR: {\r
- builder = new Builder(this);\r
- final EditText dirNameInput = new EditText(getBaseContext());\r
- builder.setView(dirNameInput);\r
- builder.setTitle(R.string.uploader_info_dirname);\r
- int typed_color = getResources().getColor(R.color.setup_text_typed);\r
- dirNameInput.setTextColor(typed_color);\r
- builder.setPositiveButton(android.R.string.ok,\r
- new OnClickListener() {\r
- public void onClick(DialogInterface dialog, int which) {\r
- String directoryName = dirNameInput.getText().toString();\r
- if (directoryName.trim().length() == 0) {\r
- dialog.cancel();\r
- return;\r
- }\r
- \r
- // Figure out the path where the dir needs to be created\r
- String path;\r
- if (mCurrentDir == null) {\r
- // this is just a patch; we should ensure that mCurrentDir never is null\r
- if (!mStorageManager.fileExists(OCFile.PATH_SEPARATOR)) {\r
- OCFile file = new OCFile(OCFile.PATH_SEPARATOR);\r
- mStorageManager.saveFile(file);\r
- }\r
- mCurrentDir = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR);\r
- }\r
- path = FileDisplayActivity.this.mCurrentDir.getRemotePath();\r
- \r
- // Create directory\r
- path += directoryName + OCFile.PATH_SEPARATOR;\r
- RemoteOperation operation = new CreateFolderOperation(path, mCurrentDir.getFileId(), mStorageManager);\r
- operation.execute( AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this), \r
- FileDisplayActivity.this, \r
- FileDisplayActivity.this, \r
- mHandler,\r
- FileDisplayActivity.this);\r
- \r
- dialog.dismiss();\r
- \r
- showDialog(DIALOG_SHORT_WAIT);\r
- }\r
- });\r
- builder.setNegativeButton(R.string.common_cancel,\r
- new OnClickListener() {\r
- public void onClick(DialogInterface dialog, int which) {\r
- dialog.cancel();\r
- }\r
- });\r
- dialog = builder.create();\r
- break;\r
- }\r
- case DIALOG_SHORT_WAIT: {\r
- ProgressDialog working_dialog = new ProgressDialog(this);\r
- working_dialog.setMessage(getResources().getString(\r
- R.string.wait_a_moment));\r
- working_dialog.setIndeterminate(true);\r
- working_dialog.setCancelable(false);\r
- dialog = working_dialog;\r
- break;\r
- }\r
- case DIALOG_CHOOSE_UPLOAD_SOURCE: {\r
- final String [] items = { getString(R.string.actionbar_upload_files), \r
- getString(R.string.actionbar_upload_from_apps) }; \r
- builder = new AlertDialog.Builder(this);\r
- builder.setTitle(R.string.actionbar_upload);\r
- builder.setItems(items, new DialogInterface.OnClickListener() {\r
- public void onClick(DialogInterface dialog, int item) {\r
- if (item == 0) {\r
- //if (!mDualPane) { \r
- Intent action = new Intent(FileDisplayActivity.this, UploadFilesActivity.class);\r
- action.putExtra(UploadFilesActivity.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this));\r
- startActivityForResult(action, ACTION_SELECT_MULTIPLE_FILES);\r
- //} else {\r
- // TODO create and handle new fragment LocalFileListFragment\r
- //}\r
- } else if (item == 1) {\r
- Intent action = new Intent(Intent.ACTION_GET_CONTENT);\r
- action = action.setType("*/*")\r
- .addCategory(Intent.CATEGORY_OPENABLE);\r
- startActivityForResult(\r
- Intent.createChooser(action, getString(R.string.upload_chooser_title)),\r
- ACTION_SELECT_CONTENT_FROM_APPS);\r
- }\r
- }\r
- });\r
- dialog = builder.create();\r
- break;\r
- }\r
- case DIALOG_SSL_VALIDATOR: {\r
- dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this);\r
- break;\r
- }\r
- case DIALOG_CERT_NOT_SAVED: {\r
- builder = new AlertDialog.Builder(this);\r
- builder.setMessage(getResources().getString(R.string.ssl_validator_not_saved));\r
- builder.setCancelable(false);\r
- builder.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {\r
- @Override\r
- public void onClick(DialogInterface dialog, int which) {\r
- dialog.dismiss();\r
- };\r
- });\r
- dialog = builder.create();\r
- break;\r
- }\r
- default:\r
- dialog = null;\r
- }\r
- \r
- return dialog;\r
- }\r
- \r
- \r
- /**\r
- * Translates a content URI of an image to a physical path\r
- * on the disk\r
- * @param uri The URI to resolve\r
- * @return The path to the image or null if it could not be found\r
- */\r
- public String getPath(Uri uri) {\r
- String[] projection = { MediaStore.Images.Media.DATA };\r
- Cursor cursor = managedQuery(uri, projection, null, null, null);\r
- if (cursor != null) {\r
- int column_index = cursor\r
- .getColumnIndexOrThrow(MediaStore.Images.Media.DATA);\r
- cursor.moveToFirst();\r
- return cursor.getString(column_index);\r
- } \r
- return null;\r
- }\r
- \r
- /**\r
- * Pushes a directory to the drop down list\r
- * @param directory to push\r
- * @throws IllegalArgumentException If the {@link OCFile#isDirectory()} returns false.\r
- */\r
- public void pushDirname(OCFile directory) {\r
- if(!directory.isDirectory()){\r
- throw new IllegalArgumentException("Only directories may be pushed!");\r
- }\r
- mDirectories.insert(directory.getFileName(), 0);\r
- mCurrentDir = directory;\r
- }\r
- \r
- /**\r
- * Pops a directory name from the drop down list\r
- * @return True, unless the stack is empty\r
- */\r
- public boolean popDirname() {\r
- mDirectories.remove(mDirectories.getItem(0));\r
- return !mDirectories.isEmpty();\r
- }\r
- \r
- \r
- // Custom array adapter to override text colors\r
- private class CustomArrayAdapter<T> extends ArrayAdapter<T> {\r
- \r
- public CustomArrayAdapter(FileDisplayActivity ctx, int view) {\r
- super(ctx, view);\r
- }\r
- \r
- public View getView(int position, View convertView, ViewGroup parent) {\r
- View v = super.getView(position, convertView, parent);\r
- \r
- ((TextView) v).setTextColor(getResources().getColorStateList(\r
- android.R.color.white));\r
- return v;\r
- }\r
- \r
- public View getDropDownView(int position, View convertView,\r
- ViewGroup parent) {\r
- View v = super.getDropDownView(position, convertView, parent);\r
- \r
- ((TextView) v).setTextColor(getResources().getColorStateList(\r
- android.R.color.white));\r
- \r
- return v;\r
- }\r
- \r
- }\r
- \r
- private class SyncBroadcastReceiver extends BroadcastReceiver {\r
- \r
- /**\r
- * {@link BroadcastReceiver} to enable syncing feedback in UI\r
- */\r
- @Override\r
- public void onReceive(Context context, Intent intent) {\r
- boolean inProgress = intent.getBooleanExtra(\r
- FileSyncService.IN_PROGRESS, false);\r
- String accountName = intent\r
- .getStringExtra(FileSyncService.ACCOUNT_NAME);\r
- \r
- Log.d("FileDisplay", "sync of account " + accountName\r
- + " is in_progress: " + inProgress);\r
- \r
- if (accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name)) { \r
- \r
- String synchFolderRemotePath = intent.getStringExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH); \r
- \r
- boolean fillBlankRoot = false;\r
- if (mCurrentDir == null) {\r
- mCurrentDir = mStorageManager.getFileByPath("/");\r
- fillBlankRoot = (mCurrentDir != null);\r
- }\r
- \r
- if ((synchFolderRemotePath != null && mCurrentDir != null && (mCurrentDir.getRemotePath().equals(synchFolderRemotePath)))\r
- || fillBlankRoot ) {\r
- if (!fillBlankRoot) \r
- mCurrentDir = getStorageManager().getFileByPath(synchFolderRemotePath);\r
- OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager()\r
- .findFragmentById(R.id.fileList);\r
- if (fileListFragment != null) {\r
- fileListFragment.listDirectory(mCurrentDir);\r
- }\r
- }\r
- \r
- setSupportProgressBarIndeterminateVisibility(inProgress);\r
- removeStickyBroadcast(intent);\r
- \r
- }\r
- \r
- RemoteOperationResult synchResult = (RemoteOperationResult)intent.getSerializableExtra(FileSyncService.SYNC_RESULT);\r
- if (synchResult != null) {\r
- if (synchResult.getCode().equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED)) {\r
- mLastSslUntrustedServerResult = synchResult;\r
- showDialog(DIALOG_SSL_VALIDATOR); \r
- }\r
- }\r
- }\r
- }\r
- \r
- \r
- private class UploadFinishReceiver extends BroadcastReceiver {\r
- /**\r
- * Once the file upload has finished -> update view\r
- * @author David A. Velasco\r
- * {@link BroadcastReceiver} to enable upload feedback in UI\r
- */\r
- @Override\r
- public void onReceive(Context context, Intent intent) {\r
- String uploadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);\r
- String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME);\r
- boolean sameAccount = accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name);\r
- boolean isDescendant = (mCurrentDir != null) && (uploadedRemotePath != null) && (uploadedRemotePath.startsWith(mCurrentDir.getRemotePath()));\r
- if (sameAccount && isDescendant) {\r
- OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);\r
- if (fileListFragment != null) { \r
- fileListFragment.listDirectory();\r
- }\r
- }\r
- }\r
- \r
- }\r
- \r
- \r
- /**\r
- * Once the file download has finished -> update view\r
- */\r
- private class DownloadFinishReceiver extends BroadcastReceiver {\r
- @Override\r
- public void onReceive(Context context, Intent intent) {\r
- String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);\r
- String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);\r
- boolean sameAccount = accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name);\r
- boolean isDescendant = (mCurrentDir != null) && (downloadedRemotePath != null) && (downloadedRemotePath.startsWith(mCurrentDir.getRemotePath()));\r
- if (sameAccount && isDescendant) {\r
- OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);\r
- if (fileListFragment != null) { \r
- fileListFragment.listDirectory();\r
- }\r
- }\r
- }\r
- }\r
- \r
- \r
- \r
- \r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public DataStorageManager getStorageManager() {\r
- return mStorageManager;\r
- }\r
- \r
- \r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public void onDirectoryClick(OCFile directory) {\r
- pushDirname(directory);\r
- ActionBar actionBar = getSupportActionBar();\r
- actionBar.setDisplayHomeAsUpEnabled(true);\r
- \r
- if (mDualPane) {\r
- // Resets the FileDetailsFragment on Tablets so that it always displays\r
- FileDetailFragment fileDetails = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- if (fileDetails != null && !fileDetails.isEmpty()) {\r
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
- transaction.remove(fileDetails);\r
- transaction.add(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG);\r
- transaction.commit();\r
- }\r
- }\r
- }\r
- \r
- \r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public void onFileClick(OCFile file) {\r
- \r
- // If we are on a large device -> update fragment\r
- if (mDualPane) {\r
- // buttons in the details view are problematic when trying to reuse an existing fragment; create always a new one solves some of them, BUT no all; downloads are 'dangerous'\r
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
- transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);\r
- transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);\r
- transaction.commit();\r
- \r
- } else { // small or medium screen device -> new Activity\r
- Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);\r
- showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file);\r
- showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));\r
- startActivity(showDetailsIntent);\r
- }\r
- }\r
- \r
- \r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public OCFile getInitialDirectory() {\r
- return mCurrentDir;\r
- }\r
- \r
- \r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public void onFileStateChanged() {\r
- OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);\r
- if (fileListFragment != null) { \r
- fileListFragment.listDirectory();\r
- }\r
- }\r
- \r
- \r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public FileDownloaderBinder getFileDownloaderBinder() {\r
- return mDownloaderBinder;\r
- }\r
- \r
- \r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public FileUploaderBinder getFileUploaderBinder() {\r
- return mUploaderBinder;\r
- }\r
- \r
- \r
- /** Defines callbacks for service binding, passed to bindService() */\r
- private class ListServiceConnection implements ServiceConnection {\r
- \r
- @Override\r
- public void onServiceConnected(ComponentName component, IBinder service) {\r
- if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloader.class))) {\r
- Log.d(TAG, "Download service connected");\r
- mDownloaderBinder = (FileDownloaderBinder) service;\r
- } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) {\r
- Log.d(TAG, "Upload service connected");\r
- mUploaderBinder = (FileUploaderBinder) service;\r
- } else {\r
- return;\r
- }\r
- // a new chance to get the mDownloadBinder through getFileDownloadBinder() - THIS IS A MESS\r
- if (mFileList != null)\r
- mFileList.listDirectory();\r
- if (mDualPane) {\r
- FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- if (fragment != null)\r
- fragment.updateFileDetails(false);\r
- }\r
- }\r
- \r
- @Override\r
- public void onServiceDisconnected(ComponentName component) {\r
- if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloader.class))) {\r
- Log.d(TAG, "Download service disconnected");\r
- mDownloaderBinder = null;\r
- } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) {\r
- Log.d(TAG, "Upload service disconnected");\r
- mUploaderBinder = null;\r
- }\r
- }\r
- }; \r
- \r
- \r
- \r
- /**\r
- * Launch an intent to request the PIN code to the user before letting him use the app\r
- */\r
- private void requestPinCode() {\r
- boolean pinStart = false;\r
- SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());\r
- pinStart = appPrefs.getBoolean("set_pincode", false);\r
- if (pinStart) {\r
- Intent i = new Intent(getApplicationContext(), PinCodeActivity.class);\r
- i.putExtra(PinCodeActivity.EXTRA_ACTIVITY, "FileDisplayActivity");\r
- startActivity(i);\r
- }\r
- }\r
- \r
- \r
- @Override\r
- public void onSavedCertificate() {\r
- startSynchronization(); \r
- }\r
- \r
- \r
- @Override\r
- public void onFailedSavingCertificate() {\r
- showDialog(DIALOG_CERT_NOT_SAVED);\r
- }\r
- \r
- \r
- /**\r
- * Updates the view associated to the activity after the finish of some operation over files\r
- * in the current account.\r
- * \r
- * @param operation Removal operation performed.\r
- * @param result Result of the removal.\r
- */\r
- @Override\r
- public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {\r
- if (operation instanceof RemoveFileOperation) {\r
- onRemoveFileOperationFinish((RemoveFileOperation)operation, result);\r
- \r
- } else if (operation instanceof RenameFileOperation) {\r
- onRenameFileOperationFinish((RenameFileOperation)operation, result);\r
- \r
- } else if (operation instanceof SynchronizeFileOperation) {\r
- onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result);\r
- \r
- } else if (operation instanceof CreateFolderOperation) {\r
- onCreateFolderOperationFinish((CreateFolderOperation)operation, result);\r
- }\r
- }\r
- \r
- /**\r
- * Updates the view associated to the activity after the finish of an operation trying create a new folder\r
- * \r
- * @param operation Creation operation performed.\r
- * @param result Result of the creation.\r
- */\r
- private void onCreateFolderOperationFinish(CreateFolderOperation operation, RemoteOperationResult result) {\r
- if (result.isSuccess()) {\r
- dismissDialog(DIALOG_SHORT_WAIT);\r
- mFileList.listDirectory();\r
- \r
- } else {\r
- dismissDialog(DIALOG_SHORT_WAIT);\r
- try {\r
- Toast msg = Toast.makeText(FileDisplayActivity.this, R.string.create_dir_fail_msg, Toast.LENGTH_LONG); \r
- msg.show();\r
- \r
- } catch (NotFoundException e) {\r
- Log.e(TAG, "Error while trying to show fail message " , e);\r
- }\r
- }\r
- }\r
- \r
- \r
- /**\r
- * Updates the view associated to the activity after the finish of an operation trying to remove a \r
- * file. \r
- * \r
- * @param operation Removal operation performed.\r
- * @param result Result of the removal.\r
- */\r
- private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) {\r
- dismissDialog(DIALOG_SHORT_WAIT);\r
- if (result.isSuccess()) {\r
- Toast msg = Toast.makeText(this, R.string.remove_success_msg, Toast.LENGTH_LONG);\r
- msg.show();\r
- OCFile removedFile = operation.getFile();\r
- if (mDualPane) {\r
- FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- if (details != null && removedFile.equals(details.getDisplayedFile()) ) {\r
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\r
- transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null)); // empty FileDetailFragment\r
- transaction.commit();\r
- }\r
- }\r
- if (mStorageManager.getFileById(removedFile.getParentId()).equals(mCurrentDir)) {\r
- mFileList.listDirectory();\r
- }\r
- \r
- } else {\r
- Toast msg = Toast.makeText(this, R.string.remove_fail_msg, Toast.LENGTH_LONG); \r
- msg.show();\r
- if (result.isSslRecoverableException()) {\r
- mLastSslUntrustedServerResult = result;\r
- showDialog(DIALOG_SSL_VALIDATOR); \r
- }\r
- }\r
- }\r
- \r
- /**\r
- * Updates the view associated to the activity after the finish of an operation trying to rename a \r
- * file. \r
- * \r
- * @param operation Renaming operation performed.\r
- * @param result Result of the renaming.\r
- */\r
- private void onRenameFileOperationFinish(RenameFileOperation operation, RemoteOperationResult result) {\r
- dismissDialog(DIALOG_SHORT_WAIT);\r
- OCFile renamedFile = operation.getFile();\r
- if (result.isSuccess()) {\r
- if (mDualPane) {\r
- FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- if (details != null && renamedFile.equals(details.getDisplayedFile()) ) {\r
- details.updateFileDetails(renamedFile, AccountUtils.getCurrentOwnCloudAccount(this));\r
- }\r
- }\r
- if (mStorageManager.getFileById(renamedFile.getParentId()).equals(mCurrentDir)) {\r
- mFileList.listDirectory();\r
- }\r
- \r
- } else {\r
- if (result.getCode().equals(ResultCode.INVALID_LOCAL_FILE_NAME)) {\r
- Toast msg = Toast.makeText(this, R.string.rename_local_fail_msg, Toast.LENGTH_LONG); \r
- msg.show();\r
- // TODO throw again the new rename dialog\r
- } else {\r
- Toast msg = Toast.makeText(this, R.string.rename_server_fail_msg, Toast.LENGTH_LONG); \r
- msg.show();\r
- if (result.isSslRecoverableException()) {\r
- mLastSslUntrustedServerResult = result;\r
- showDialog(DIALOG_SSL_VALIDATOR); \r
- }\r
- }\r
- }\r
- }\r
- \r
- \r
- private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) {\r
- dismissDialog(DIALOG_SHORT_WAIT);\r
- OCFile syncedFile = operation.getLocalFile();\r
- if (!result.isSuccess()) {\r
- if (result.getCode() == ResultCode.SYNC_CONFLICT) {\r
- Intent i = new Intent(this, ConflictsResolveActivity.class);\r
- i.putExtra(ConflictsResolveActivity.EXTRA_FILE, syncedFile);\r
- i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));\r
- startActivity(i);\r
- \r
- } else {\r
- Toast msg = Toast.makeText(this, R.string.sync_file_fail_msg, Toast.LENGTH_LONG); \r
- msg.show();\r
- }\r
- \r
- } else {\r
- if (operation.transferWasRequested()) {\r
- mFileList.listDirectory();\r
- onTransferStateChanged(syncedFile, true, true);\r
- \r
- } else {\r
- Toast msg = Toast.makeText(this, R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG); \r
- msg.show();\r
- }\r
- }\r
- }\r
- \r
- \r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading) {\r
- /*OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);\r
- if (fileListFragment != null) { \r
- fileListFragment.listDirectory();\r
- }*/\r
- if (mDualPane) {\r
- FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);\r
- if (details != null && file.equals(details.getDisplayedFile()) ) {\r
- if (downloading || uploading) {\r
- details.updateFileDetails(file, AccountUtils.getCurrentOwnCloudAccount(this));\r
- } else {\r
- details.updateFileDetails(downloading || uploading);\r
- }\r
- }\r
- }\r
- }\r
- \r
- \r
- \r
- \r
- \r
- }\r
+ /* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+ 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.AlertDialog.Builder;
+ 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.DialogInterface.OnClickListener;
+ import android.content.Intent;
+ import android.content.IntentFilter;
+ import android.content.ServiceConnection;
+ import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.content.pm.PackageInfo;
+ 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.FragmentTransaction;
-import android.util.Log;
+ import android.view.View;
+ import android.view.ViewGroup;
+ import android.widget.ArrayAdapter;
-import android.widget.EditText;
+ import android.widget.TextView;
+ import android.widget.Toast;
+
+ import com.actionbarsherlock.app.ActionBar;
+ import com.actionbarsherlock.app.ActionBar.OnNavigationListener;
+ import com.actionbarsherlock.app.SherlockFragmentActivity;
+ import com.actionbarsherlock.view.Menu;
+ import com.actionbarsherlock.view.MenuInflater;
+ import com.actionbarsherlock.view.MenuItem;
+ import com.actionbarsherlock.view.Window;
+ import com.owncloud.android.AccountUtils;
+ import com.owncloud.android.Log_OC;
+ import com.owncloud.android.R;
-import com.owncloud.android.authenticator.AccountAuthenticator;
++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.FileDownloader;
+ import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
+ import com.owncloud.android.files.services.FileObserverService;
+ import com.owncloud.android.files.services.FileUploader;
+ import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
-import com.owncloud.android.network.OwnCloudClientUtils;
++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.ChangelogDialog;
+ import com.owncloud.android.ui.dialog.EditNameDialog;
+ 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.PreviewImageFragment;
+ import com.owncloud.android.ui.preview.PreviewMediaFragment;
+
-import eu.alefzero.webdav.WebdavClient;
-
+ /**
+ * Displays, what files the user has available in his ownCloud.
+ *
+ * @author Bartek Przybylski
+ * @author David A. Velasco
+ */
+
+ public class FileDisplayActivity extends SherlockFragmentActivity implements
+ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener, OnSslValidatorListener, OnRemoteOperationListener, EditNameDialogListener {
+
+ private ArrayAdapter<String> mDirectories;
+ private OCFile mCurrentDir = null;
+ private OCFile mCurrentFile = null;
+
+ private DataStorageManager mStorageManager;
+ 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 OCFileListFragment mFileList;
+
+ private boolean mDualPane;
+ private boolean mBackFromCreatingFirstAccount;
+
+ private static final int DIALOG_SETUP_ACCOUNT = 0;
- private static final int DIALOG_CREATE_DIR = 1;
- public static final int DIALOG_SHORT_WAIT = 3;
- private static final int DIALOG_CHOOSE_UPLOAD_SOURCE = 4;
- private static final int DIALOG_SSL_VALIDATOR = 5;
- private static final int DIALOG_CERT_NOT_SAVED = 6;
- private static final String DIALOG_CHANGELOG_TAG = "DIALOG_CHANGELOG";
-
++ public static final int DIALOG_SHORT_WAIT = 1;
++ private static final int DIALOG_CHOOSE_UPLOAD_SOURCE = 2;
++ private static final int DIALOG_SSL_VALIDATOR = 3;
++ private static final int DIALOG_CERT_NOT_SAVED = 4;
+
+ 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";
+
+ private OCFile mWaitingToPreview;
+ private Handler mHandler;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log_OC.d(getClass().toString(), "onCreate() start");
+ super.onCreate(savedInstanceState);
+
++ mHandler = new Handler();
++
+ /// Load of parameters from received intent
+ Account account = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT);
+ if (account != null && AccountUtils.setCurrentOwnCloudAccount(this, account.name)) {
+ mCurrentDir = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE);
+ }
+
+ /// Load of saved instance state: keep this always before initDataFromCurrentAccount()
+ if(savedInstanceState != null) {
+ // TODO - test if savedInstanceState should take precedence over file in the intent ALWAYS (now), NEVER. SOME TIMES
+ mCurrentDir = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_FILE);
+ mWaitingToPreview = (OCFile) savedInstanceState.getParcelable(FileDetailActivity.KEY_WAITING_TO_PREVIEW);
+
+ } else {
+ mWaitingToPreview = null;
+ }
+
+ if (!AccountUtils.accountsAreSetup(this)) {
+ /// no account available: FORCE ACCOUNT CREATION
+ mStorageManager = null;
+ createFirstAccount();
+
+ } else { /// at least an account is available
+
+ initDataFromCurrentAccount(); // it checks mCurrentDir and mCurrentFile with the current account
+
+ }
+
+ 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);
+
+
+ /// USER INTERFACE
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+
+ // Drop-down navigation
+ mDirectories = new CustomArrayAdapter<String>(this, R.layout.sherlock_spinner_dropdown_item);
+ OCFile currFile = mCurrentDir;
+ while(mStorageManager != null && currFile != null && currFile.getFileName() != OCFile.PATH_SEPARATOR) {
+ mDirectories.add(currFile.getFileName());
+ currFile = mStorageManager.getFileById(currFile.getParentId());
+ }
+ mDirectories.add(OCFile.PATH_SEPARATOR);
+
+ // Inflate and set the layout view
+ setContentView(R.layout.files);
+ mFileList = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);
+ mDualPane = (findViewById(R.id.file_details_container) != null);
+ if (mDualPane) {
+ if (savedInstanceState == null) initFileDetailsInDualPane();
+ } else {
+ // quick patchES to fix problem in turn from landscape to portrait, when a file is selected in the right pane
+ // TODO serious refactorization in activities and fragments providing file browsing and handling
+ if (mCurrentFile != null) {
+ onFileClick(mCurrentFile);
+ mCurrentFile = null;
+ }
+ Fragment rightPanel = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+ if (rightPanel != null) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.remove(rightPanel);
+ transaction.commit();
+ }
+ }
+
+ // Action bar setup
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setHomeButtonEnabled(true); // mandatory since Android ICS, according to the official documentation
+ actionBar.setDisplayHomeAsUpEnabled(mCurrentDir != null && mCurrentDir.getParentId() != 0);
+ actionBar.setDisplayShowTitleEnabled(false);
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
+ actionBar.setListNavigationCallbacks(mDirectories, this);
+ setSupportProgressBarIndeterminateVisibility(false); // always AFTER setContentView(...) ; to workaround bug in its implementation
+
+
- // show changelog, if needed
- //showChangeLog();
+ mBackFromCreatingFirstAccount = false;
+
+ Log_OC.d(getClass().toString(), "onCreate() end");
+ }
+
+
+ /**
- * Shows a dialog with the change log of the current version after each app update
- *
- * TODO make it permanent; by now, only to advice the workaround app for 4.1.x
- */
- private void showChangeLog() {
- if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.JELLY_BEAN) {
- final String KEY_VERSION = "version";
- SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
- int currentVersionNumber = 0;
- int savedVersionNumber = sharedPref.getInt(KEY_VERSION, 0);
- try {
- PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
- currentVersionNumber = pi.versionCode;
- } catch (Exception e) {}
-
- if (currentVersionNumber > savedVersionNumber) {
- ChangelogDialog.newInstance(true).show(getSupportFragmentManager(), DIALOG_CHANGELOG_TAG);
- Editor editor = sharedPref.edit();
- editor.putInt(KEY_VERSION, currentVersionNumber);
- editor.commit();
- }
- }
- }
-
-
- /**
+ * Launches the account creation activity. To use when no ownCloud account is available
+ */
+ private void createFirstAccount() {
+ Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT);
- intent.putExtra(android.provider.Settings.EXTRA_AUTHORITIES, new String[] { AccountAuthenticator.AUTH_TOKEN_TYPE });
++ intent.putExtra(android.provider.Settings.EXTRA_AUTHORITIES, new String[] { AccountAuthenticator.AUTHORITY });
+ startActivity(intent); // the new activity won't be created until this.onStart() and this.onResume() are finished;
+ }
+
+
+ /**
+ * Load of state dependent of the existence of an ownCloud account
+ */
+ private void initDataFromCurrentAccount() {
+ /// Storage manager initialization - access to local database
+ mStorageManager = new FileDataStorageManager(
+ AccountUtils.getCurrentOwnCloudAccount(this),
+ getContentResolver());
+
+ /// Check if mCurrentDir is a directory
+ if(mCurrentDir != null && !mCurrentDir.isDirectory()) {
+ mCurrentFile = mCurrentDir;
+ mCurrentDir = mStorageManager.getFileById(mCurrentDir.getParentId());
+ }
+
+ /// Check if mCurrentDir and mCurrentFile are in the current account, and update them
+ if (mCurrentDir != null) {
+ mCurrentDir = mStorageManager.getFileByPath(mCurrentDir.getRemotePath()); // mCurrentDir == null if it is not in the current account
+ }
+ if (mCurrentFile != null) {
+ if (mCurrentFile.fileExists()) {
+ mCurrentFile = mStorageManager.getFileByPath(mCurrentFile.getRemotePath()); // mCurrentFile == null if it is not in the current account
+ } // else : keep mCurrentFile with the received value; this is currently the case of an upload in progress, when the user presses the status notification in a landscape tablet
+ }
+
+ /// Default to root if mCurrentDir was not found
+ if (mCurrentDir == null) {
+ mCurrentDir = mStorageManager.getFileByPath("/"); // will be NULL if the database was never synchronized
+ }
+ }
+
+
+ private void initFileDetailsInDualPane() {
+ if (mDualPane && getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG) == null) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ if (mCurrentFile != null) {
+ if (PreviewMediaFragment.canBePreviewed(mCurrentFile)) {
+ if (mCurrentFile.isDown()) {
+ transaction.replace(R.id.file_details_container, new PreviewMediaFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);
+ } else {
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);
+ mWaitingToPreview = mCurrentFile;
+ }
+ } else {
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);
+ }
+ mCurrentFile = null;
+
+ } else {
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment
+ }
+ transaction.commit();
+ }
+ }
+
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mDownloadConnection != null)
+ unbindService(mDownloadConnection);
+ if (mUploadConnection != null)
+ unbindService(mUploadConnection);
+ }
+
+
+ @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: {
+ if(mCurrentDir != null && mCurrentDir.getParentId() != 0){
+ onBackPressed();
+ }
+ break;
+ }
+ default:
+ retval = super.onOptionsItemSelected(item);
+ }
+ return retval;
+ }
+
+ private void startSynchronization() {
- ContentResolver.cancelSync(null, AccountAuthenticator.AUTH_TOKEN_TYPE); // cancel the current synchronizations of any ownCloud account
++ ContentResolver.cancelSync(null, AccountAuthenticator.AUTHORITY); // cancel the current synchronizations of any ownCloud account
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+ ContentResolver.requestSync(
+ AccountUtils.getCurrentOwnCloudAccount(this),
- AccountAuthenticator.AUTH_TOKEN_TYPE, bundle);
++ AccountAuthenticator.AUTHORITY, 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
+ */
+ public void onActivityResult(int requestCode, int resultCode, Intent 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, AccountUtils.getCurrentOwnCloudAccount(this));
+ 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("FileDisplay", "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("FileDisplay", "Unexpected exception when trying to read the result of Intent.ACTION_GET_CONTENT", e);
+ e.printStackTrace();
+
+ } finally {
+ if (filepath == null) {
+ Log_OC.e("FileDisplay", "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,
+ AccountUtils.getCurrentOwnCloudAccount(this));
+ 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() {
+ if (mDirectories.getCount() <= 1) {
+ finish();
+ return;
+ }
+ popDirname();
+ mFileList.onNavigateUp();
+ mCurrentDir = mFileList.getCurrentFile();
+
+ if (mDualPane) {
+ // Resets the FileDetailsFragment on Tablets so that it always displays
+ Fragment fileFragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+ if (fileFragment != null && (fileFragment instanceof PreviewMediaFragment || !((FileDetailFragment) fileFragment).isEmpty())) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment
+ transaction.commit();
+ }
+ }
+
+ if(mCurrentDir.getParentId() == 0){
+ 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(getClass().toString(), "onSaveInstanceState() start");
+ super.onSaveInstanceState(outState);
+ outState.putParcelable(FileDetailFragment.EXTRA_FILE, mCurrentDir);
+ if (mDualPane) {
+ FileFragment fragment = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+ if (fragment != null) {
+ OCFile file = fragment.getFile();
+ if (file != null) {
+ outState.putParcelable(FileDetailFragment.EXTRA_FILE, file);
+ }
+ }
+ }
+ outState.putParcelable(FileDetailActivity.KEY_WAITING_TO_PREVIEW, mWaitingToPreview);
+ Log_OC.d(getClass().toString(), "onSaveInstanceState() end");
+ }
-
+
+ @Override
+ protected void onResume() {
+ Log_OC.d(getClass().toString(), "onResume() start");
+ super.onResume();
+
+ if (AccountUtils.accountsAreSetup(this)) {
+
+ if (mStorageManager == null) {
+ // this is necessary for handling the come back to FileDisplayActivity when the first ownCloud account is created
+ initDataFromCurrentAccount();
+ if (mDualPane) {
+ initFileDetailsInDualPane();
+ }
+ mBackFromCreatingFirstAccount = true;
+ }
+
+ // Listen for sync messages
+ IntentFilter syncIntentFilter = new IntentFilter(FileSyncService.SYNC_MESSAGE);
+ mSyncBroadcastReceiver = new SyncBroadcastReceiver();
+ registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
+
+ // Listen for upload messages
+ IntentFilter uploadIntentFilter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE);
+ mUploadFinishReceiver = new UploadFinishReceiver();
+ registerReceiver(mUploadFinishReceiver, uploadIntentFilter);
+
+ // Listen for download messages
+ IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.DOWNLOAD_ADDED_MESSAGE);
+ downloadIntentFilter.addAction(FileDownloader.DOWNLOAD_FINISH_MESSAGE);
+ mDownloadFinishReceiver = new DownloadFinishReceiver();
+ registerReceiver(mDownloadFinishReceiver, downloadIntentFilter);
+
+ // List current directory
+ mFileList.listDirectory(mCurrentDir); // TODO we should find the way to avoid the need of this (maybe it's not necessary yet; to check)
+
+ } else {
+
+ mStorageManager = null; // an invalid object will be there if all the ownCloud accounts are removed
+ showDialog(DIALOG_SETUP_ACCOUNT);
+
+ }
+ Log_OC.d(getClass().toString(), "onResume() end");
+ }
+
+
+ @Override
+ protected void onPause() {
+ Log_OC.d(getClass().toString(), "onPause() start");
+ super.onPause();
+ if (mSyncBroadcastReceiver != null) {
+ unregisterReceiver(mSyncBroadcastReceiver);
+ mSyncBroadcastReceiver = null;
+ }
+ if (mUploadFinishReceiver != null) {
+ unregisterReceiver(mUploadFinishReceiver);
+ mUploadFinishReceiver = null;
+ }
+ if (mDownloadFinishReceiver != null) {
+ unregisterReceiver(mDownloadFinishReceiver);
+ mDownloadFinishReceiver = null;
+ }
+ if (!AccountUtils.accountsAreSetup(this)) {
+ dismissDialog(DIALOG_SETUP_ACCOUNT);
+ }
+
+ Log_OC.d(getClass().toString(), "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_SETUP_ACCOUNT: {
+ builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.main_tit_accsetup);
+ builder.setMessage(R.string.main_wrn_accsetup);
+ builder.setCancelable(false);
+ builder.setPositiveButton(android.R.string.ok, new OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ createFirstAccount();
+ dialog.dismiss();
+ }
+ });
+ String message = String.format(getString(R.string.common_exit), getString(R.string.app_name));
+ builder.setNegativeButton(message, new OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ finish();
+ }
+ });
+ //builder.setNegativeButton(android.R.string.cancel, this);
+ dialog = builder.create();
+ break;
+ }
- case DIALOG_CREATE_DIR: {
- builder = new Builder(this);
- final EditText dirNameInput = new EditText(getBaseContext());
- builder.setView(dirNameInput);
- builder.setTitle(R.string.uploader_info_dirname);
- int typed_color = getResources().getColor(R.color.setup_text_typed);
- dirNameInput.setTextColor(typed_color);
- builder.setPositiveButton(android.R.string.ok,
- new OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- String directoryName = dirNameInput.getText().toString();
- if (directoryName.trim().length() == 0) {
- dialog.cancel();
- return;
- }
-
- // Figure out the path where the dir needs to be created
- String path;
- if (mCurrentDir == null) {
- // this is just a patch; we should ensure that mCurrentDir never is null
- if (!mStorageManager.fileExists(OCFile.PATH_SEPARATOR)) {
- OCFile file = new OCFile(OCFile.PATH_SEPARATOR);
- mStorageManager.saveFile(file);
- }
- mCurrentDir = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR);
- }
- path = FileDisplayActivity.this.mCurrentDir.getRemotePath();
-
- // Create directory
- path += directoryName + OCFile.PATH_SEPARATOR;
- Thread thread = new Thread(new DirectoryCreator(path, AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this), new Handler()));
- thread.start();
-
- dialog.dismiss();
-
- showDialog(DIALOG_SHORT_WAIT);
- }
- });
- builder.setNegativeButton(R.string.common_cancel,
- new OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- dialog.cancel();
- }
- });
- dialog = builder.create();
- break;
- }
+ 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,
+ AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this));
+ 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) {
+ Account account = AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this);
+ Intent action = new Intent(FileDisplayActivity.this, InstantUploadActivity.class);
+ action.putExtra(FileUploader.KEY_ACCOUNT, account);
+ 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;
+ }
+
+
+ /**
+ * 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);
+ 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();
+ }
+
- private class DirectoryCreator implements Runnable {
- private String mTargetPath;
- private Account mAccount;
- private Handler mHandler;
-
- public DirectoryCreator(String targetPath, Account account, Handler handler) {
- mTargetPath = targetPath;
- mAccount = account;
- mHandler = handler;
- }
-
- @Override
- public void run() {
- WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext());
- boolean created = wdc.createDirectory(mTargetPath);
- if (created) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- dismissDialog(DIALOG_SHORT_WAIT);
-
- // Save new directory in local database
- OCFile newDir = new OCFile(mTargetPath);
- newDir.setMimetype("DIR");
- newDir.setParentId(mCurrentDir.getFileId());
- mStorageManager.saveFile(newDir);
-
- // Display the new folder right away
- mFileList.listDirectory();
- }
- });
-
- } else {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- dismissDialog(DIALOG_SHORT_WAIT);
- 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);
- }
- }
- });
- }
- }
-
- }
-
+ // Custom array adapter to override text colors
+ private class CustomArrayAdapter<T> extends ArrayAdapter<T> {
+
+ 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("FileDisplay", "sync of account " + accountName + " is in_progress: " + inProgress);
+
+ if (accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name)) {
+
+ String synchFolderRemotePath = intent.getStringExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH);
+
+ boolean fillBlankRoot = false;
+ if (mCurrentDir == null) {
+ mCurrentDir = mStorageManager.getFileByPath("/");
+ fillBlankRoot = (mCurrentDir != null);
+ }
+
+ if ((synchFolderRemotePath != null && mCurrentDir != null && (mCurrentDir.getRemotePath().equals(synchFolderRemotePath)))
+ || fillBlankRoot ) {
+ if (!fillBlankRoot)
+ mCurrentDir = getStorageManager().getFileByPath(synchFolderRemotePath);
+ OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.fileList);
+ if (fileListFragment != null) {
+ fileListFragment.listDirectory(mCurrentDir);
+ }
+ }
+
+ setSupportProgressBarIndeterminateVisibility(inProgress);
+ if (mBackFromCreatingFirstAccount) {
+ // awful patch to fix problem with visibility of progress circle with the first refresh of the first account
+ // TODO - kill this Activity when the first account has to be created instead of stack the account creation on it
+ getSupportActionBar().hide();
+ getSupportActionBar().show();
+ mBackFromCreatingFirstAccount = false;
+ }
+ 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 = accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name);
+ boolean isDescendant = (mCurrentDir != null) && (uploadedRemotePath != null) && (uploadedRemotePath.startsWith(mCurrentDir.getRemotePath()));
+ if (sameAccount && isDescendant) {
+ OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);
+ if (fileListFragment != null) {
+ fileListFragment.listDirectory();
+ }
+ }
+ }
+
+ }
+
+
+ /**
+ * 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) {
+ updateLeftPanel();
+ if (mDualPane) {
+ updateRightPanel(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false));
+ }
+ }
+
+ removeStickyBroadcast(intent);
+ }
+
+ private boolean isDescendant(String downloadedRemotePath) {
+ return (mCurrentDir != null && downloadedRemotePath != null && downloadedRemotePath.startsWith(mCurrentDir.getRemotePath()));
+ }
+
+ private boolean isSameAccount(Context context, Intent intent) {
+ String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);
+ return (accountName != null && accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name));
+ }
+ }
+
+
+ protected void updateLeftPanel() {
+ OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);
+ if (fileListFragment != null) {
+ fileListFragment.listDirectory();
+ }
+ }
+
+ protected void updateRightPanel(String downloadEvent, String downloadedRemotePath, boolean success) {
+ Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+ boolean waitedPreview = (mWaitingToPreview != null && mWaitingToPreview.getRemotePath().equals(downloadedRemotePath));
+ if (fragment != null && fragment instanceof FileDetailFragment) {
+ FileDetailFragment detailsFragment = (FileDetailFragment) fragment;
+ 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(FileDownloader.DOWNLOAD_ADDED_MESSAGE)) {
+ // grant that the right panel updates the progress bar
+ detailsFragment.listenForTransferProgress();
+ detailsFragment.updateFileDetails(true, false);
+
+ } else if (downloadEvent.equals(FileDownloader.DOWNLOAD_FINISH_MESSAGE)) {
+ // update the right panel
+ if (success && waitedPreview) {
+ mWaitingToPreview = mStorageManager.getFileById(mWaitingToPreview.getFileId()); // update the file from database, for the local storage path
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.file_details_container, new PreviewMediaFragment(mWaitingToPreview, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);
+ transaction.commit();
+ mWaitingToPreview = null;
+
+ } else {
+ detailsFragment.updateFileDetails(false, (success));
+ }
+ }
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public DataStorageManager getStorageManager() {
+ return mStorageManager;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDirectoryClick(OCFile directory) {
+ pushDirname(directory);
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+
+ if (mDualPane) {
+ // Resets the FileDetailsFragment on Tablets so that it always displays
+ Fragment fileFragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+ if (fileFragment != null && (fileFragment instanceof PreviewMediaFragment || !((FileDetailFragment) fileFragment).isEmpty())) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment
+ transaction.commit();
+ }
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onFileClick(OCFile file) {
+ if (file != null && PreviewImageFragment.canBePreviewed(file)) {
+ // preview image - it handles the download, if needed
+ startPreviewImage(file);
+
+ } else if (file != null && PreviewMediaFragment.canBePreviewed(file)) {
+ if (file.isDown()) {
+ // general preview
+ startMediaPreview(file);
+
+ } else {
+ // automatic download, preview on finish
+ startDownloadForPreview(file);
+
+ }
+ } else {
+ // details view
+ startDetails(file);
+ }
+ }
+
+ private void startPreviewImage(OCFile file) {
+ Intent showDetailsIntent = new Intent(this, PreviewImageActivity.class);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));
+ startActivity(showDetailsIntent);
+ }
+
+ private void startMediaPreview(OCFile file) {
+ if (mDualPane) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.file_details_container, new PreviewMediaFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);
+ transaction.commit();
+
+ } else {
+ Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));
+ startActivity(showDetailsIntent);
+ }
+ }
+
+ private void startDownloadForPreview(OCFile file) {
+ if (mDualPane) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);
+ transaction.commit();
+ mWaitingToPreview = file;
+ requestForDownload();
+
+ } else {
+ Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));
+ startActivity(showDetailsIntent);
+ }
+ }
+
+
+ private void startDetails(OCFile file) {
+ if (mDualPane && !file.isImage()) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);
+ transaction.commit();
+ } else {
+ Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));
+ startActivity(showDetailsIntent);
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public OCFile getInitialDirectory() {
+ return mCurrentDir;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onFileStateChanged() {
+ OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);
+ if (fileListFragment != null) {
+ fileListFragment.listDirectory();
+ }
+ }
+
+
+ /**
+ * {@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
+ if (mFileList != null)
+ mFileList.listDirectory();
+ if (mDualPane) {
+ Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+ if (fragment != null && fragment instanceof FileDetailFragment) {
+ FileDetailFragment detailFragment = (FileDetailFragment)fragment;
+ 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) {
+ dismissDialog(DIALOG_SHORT_WAIT);
+ if (result.isSuccess()) {
+ Toast msg = Toast.makeText(this, R.string.remove_success_msg, Toast.LENGTH_LONG);
+ msg.show();
+ OCFile removedFile = operation.getFile();
+ if (mDualPane) {
+ FileFragment details = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+ if (details != null && removedFile.equals(details.getFile())) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null)); // empty FileDetailFragment
+ transaction.commit();
+ }
+ }
+ if (mStorageManager.getFileById(removedFile.getParentId()).equals(mCurrentDir)) {
+ mFileList.listDirectory();
+ }
+
+ } 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()) {
++ dismissDialog(DIALOG_SHORT_WAIT);
++ mFileList.listDirectory();
++
++ } else {
++ dismissDialog(DIALOG_SHORT_WAIT);
++ 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) {
+ dismissDialog(DIALOG_SHORT_WAIT);
+ OCFile renamedFile = operation.getFile();
+ if (result.isSuccess()) {
+ if (mDualPane) {
+ FileFragment details = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+ if (details != null && details instanceof FileDetailFragment && renamedFile.equals(details.getFile()) ) {
+ ((FileDetailFragment) details).updateFileDetails(renamedFile, AccountUtils.getCurrentOwnCloudAccount(this));
+ }
+ }
+ if (mStorageManager.getFileById(renamedFile.getParentId()).equals(mCurrentDir)) {
+ mFileList.listDirectory();
+ }
+
+ } 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) {
+ dismissDialog(DIALOG_SHORT_WAIT);
+ 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, AccountUtils.getCurrentOwnCloudAccount(this));
+ startActivity(i);
+
+ } else {
+ Toast msg = Toast.makeText(this, R.string.sync_file_fail_msg, Toast.LENGTH_LONG);
+ msg.show();
+ }
+
+ } else {
+ if (operation.transferWasRequested()) {
+ mFileList.listDirectory();
+ 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) {
+ /*OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);
+ if (fileListFragment != null) {
+ fileListFragment.listDirectory();
+ }*/
+ if (mDualPane) {
+ FileFragment details = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+ if (details != null && details instanceof FileDetailFragment && file.equals(details.getFile()) ) {
+ if (downloading || uploading) {
+ ((FileDetailFragment)details).updateFileDetails(file, AccountUtils.getCurrentOwnCloudAccount(this));
+ } else {
+ ((FileDetailFragment)details).updateFileDetails(false, true);
+ }
+ }
+ }
+ }
+
+
+ @Override
+ public void showFragmentWithDetails(OCFile file) {
+ if (mDualPane) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);
+ transaction.commit();
+
+ } else {
+ Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));
+ showDetailsIntent.putExtra(FileDetailActivity.EXTRA_MODE, FileDetailActivity.MODE_DETAILS);
+ startActivity(showDetailsIntent);
+ }
+ }
+
+ public void onDismiss(EditNameDialog dialog) {
+ //dialog.dismiss();
+ if (dialog.getResult()) {
+ String newDirectoryName = dialog.getNewFilename().trim();
- Log.d(TAG, "'create directory' dialog dismissed with new name " + newDirectoryName);
++ Log_OC.d(TAG, "'create directory' dialog dismissed with new name " + newDirectoryName);
+ if (newDirectoryName.length() > 0) {
+ String path;
+ if (mCurrentDir == null) {
+ // this is just a patch; we should ensure that mCurrentDir never is null
+ if (!mStorageManager.fileExists(OCFile.PATH_SEPARATOR)) {
+ OCFile file = new OCFile(OCFile.PATH_SEPARATOR);
+ mStorageManager.saveFile(file);
+ }
+ mCurrentDir = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR);
+ }
+ path = FileDisplayActivity.this.mCurrentDir.getRemotePath();
+
+ // Create directory
+ path += newDirectoryName + OCFile.PATH_SEPARATOR;
- Thread thread = new Thread(new DirectoryCreator(path, AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this), new Handler()));
- thread.start();
++ RemoteOperation operation = new CreateFolderOperation(path, mCurrentDir.getFileId(), mStorageManager);
++ operation.execute( AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this),
++ FileDisplayActivity.this,
++ FileDisplayActivity.this,
++ mHandler,
++ FileDisplayActivity.this);
+
+ showDialog(DIALOG_SHORT_WAIT);
+ }
+ }
+ }
-
++
++
+ private void requestForDownload() {
+ Account account = AccountUtils.getCurrentOwnCloudAccount(this);
+ 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);
+ }
+ }
+
+
+ }
--- /dev/null
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
+ /* ownCloud Android client application
+ * Copyright (C) 2012-2013 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
-import android.util.Log;
++ * it under the terms of the GNU General Public License version 2,
++ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+ package com.owncloud.android.ui.activity;
+
+ import 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;
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License. (at
- * your option) any later version.
- *
- * 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 de/
+ 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 com.owncloud.android.AccountUtils;
+ import com.owncloud.android.Log_OC;
+ import com.owncloud.android.R;
+ import com.owncloud.android.db.DbHandler;
+ import com.owncloud.android.files.InstantUploadBroadcastReceiver;
+ import com.owncloud.android.files.services.FileUploader;
+ import com.owncloud.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
- Log.d(LOG_TAG, message);
+ */
+ 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<String> fileList = null;
+ CheckBox failed_upload_all_cb;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.failed_upload_files);
+
+ Button delete_all_btn = (Button) findViewById(R.id.failed_upload_delete_all_btn);
+ delete_all_btn.setOnClickListener(getDeleteListner());
+ Button retry_all_btn = (Button) findViewById(R.id.failed_upload_retry_all_btn);
+ retry_all_btn.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<String>();
+ 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.owncloud_white);
+ 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<CheckBox>
+ */
+ private List<CheckBox> getCheckboxList() {
+ List<CheckBox> list = new ArrayList<CheckBox>();
+ 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<CheckBox> 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<CheckBox> 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<CheckBox> 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.owncloud_white);
+ 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.owncloud_white);
+ 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.owncloud_white);
+ 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;
+ }
+ }
+
+ }
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.DialogFragment;
--import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
--import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
--import android.util.Log;
import com.actionbarsherlock.app.SherlockDialogFragment;
+ import com.owncloud.android.Log_OC;
public class ConfirmationDialogFragment extends SherlockDialogFragment {
- /* ownCloud Android client application\r
- * Copyright (C) 2011 Bartek Przybylski\r
- * Copyright (C) 2012-2013 ownCloud Inc.\r
- *\r
- * This program is free software: you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 2 of the License, or\r
- * (at your option) any later version.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program. If not, see <http://www.gnu.org/licenses/>.\r
- *\r
- */\r
- package com.owncloud.android.ui.fragment;\r
- \r
- import java.io.File;\r
- \r
- import org.apache.commons.httpclient.Credentials;\r
- \r
- import android.accounts.Account;\r
- import android.accounts.AccountManager;\r
- import android.annotation.SuppressLint;\r
- import android.app.Activity;\r
- import android.content.ActivityNotFoundException;\r
- import android.content.BroadcastReceiver;\r
- import android.content.Context;\r
- import android.content.Intent;\r
- import android.content.IntentFilter;\r
- import android.graphics.Bitmap;\r
- import android.graphics.BitmapFactory;\r
- import android.graphics.BitmapFactory.Options;\r
- import android.graphics.Point;\r
- import android.net.Uri;\r
- import android.os.AsyncTask;\r
- import android.os.Bundle;\r
- import android.os.Handler;\r
- import android.support.v4.app.DialogFragment;\r
- import android.support.v4.app.FragmentTransaction;\r
- import android.util.Log;\r
- import android.view.Display;\r
- import android.view.LayoutInflater;\r
- import android.view.View;\r
- import android.view.View.OnClickListener;\r
- import android.view.ViewGroup;\r
- import android.webkit.MimeTypeMap;\r
- import android.widget.Button;\r
- import android.widget.CheckBox;\r
- import android.widget.ImageView;\r
- import android.widget.TextView;\r
- import android.widget.Toast;\r
- \r
- import com.actionbarsherlock.app.SherlockFragment;\r
- import com.owncloud.android.DisplayUtils;\r
- import com.owncloud.android.authentication.AccountAuthenticator;\r
- import com.owncloud.android.datamodel.FileDataStorageManager;\r
- import com.owncloud.android.datamodel.OCFile;\r
- import com.owncloud.android.files.services.FileDownloader;\r
- import com.owncloud.android.files.services.FileObserverService;\r
- import com.owncloud.android.files.services.FileUploader;\r
- import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;\r
- import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;\r
- import com.owncloud.android.network.BearerCredentials;\r
- import com.owncloud.android.operations.OnRemoteOperationListener;\r
- import com.owncloud.android.operations.RemoteOperation;\r
- import com.owncloud.android.operations.RemoteOperationResult;\r
- import com.owncloud.android.operations.RemoteOperationResult.ResultCode;\r
- import com.owncloud.android.operations.RemoveFileOperation;\r
- import com.owncloud.android.operations.RenameFileOperation;\r
- import com.owncloud.android.operations.SynchronizeFileOperation;\r
- import com.owncloud.android.ui.activity.ConflictsResolveActivity;\r
- import com.owncloud.android.ui.activity.FileDetailActivity;\r
- import com.owncloud.android.ui.activity.FileDisplayActivity;\r
- import com.owncloud.android.ui.activity.TransferServiceGetter;\r
- import com.owncloud.android.ui.dialog.EditNameDialog;\r
- import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;\r
- \r
- import com.owncloud.android.R;\r
- import eu.alefzero.webdav.WebdavUtils;\r
- \r
- /**\r
- * This Fragment is used to display the details about a file.\r
- * \r
- * @author Bartek Przybylski\r
- * \r
- */\r
- public class FileDetailFragment extends SherlockFragment implements\r
- OnClickListener, ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener, EditNameDialogListener {\r
- \r
- public static final String EXTRA_FILE = "FILE";\r
- public static final String EXTRA_ACCOUNT = "ACCOUNT";\r
- \r
- private FileDetailFragment.ContainerActivity mContainerActivity;\r
- \r
- private int mLayout;\r
- private View mView;\r
- private OCFile mFile;\r
- private Account mAccount;\r
- private FileDataStorageManager mStorageManager;\r
- private ImageView mPreview;\r
- \r
- private DownloadFinishReceiver mDownloadFinishReceiver;\r
- private UploadFinishReceiver mUploadFinishReceiver;\r
- \r
- private Handler mHandler;\r
- private RemoteOperation mLastRemoteOperation;\r
- private DialogFragment mCurrentDialog;\r
- \r
- private static final String TAG = FileDetailFragment.class.getSimpleName();\r
- public static final String FTAG = "FileDetails"; \r
- public static final String FTAG_CONFIRMATION = "REMOVE_CONFIRMATION_FRAGMENT";\r
- \r
- \r
- /**\r
- * Creates an empty details fragment.\r
- * \r
- * It's necessary to keep a public constructor without parameters; the system uses it when tries to reinstantiate a fragment automatically. \r
- */\r
- public FileDetailFragment() {\r
- mFile = null;\r
- mAccount = null;\r
- mStorageManager = null;\r
- mLayout = R.layout.file_details_empty;\r
- }\r
- \r
- \r
- /**\r
- * Creates a details fragment.\r
- * \r
- * When 'fileToDetail' or 'ocAccount' are null, creates a dummy layout (to use when a file wasn't tapped before).\r
- * \r
- * @param fileToDetail An {@link OCFile} to show in the fragment\r
- * @param ocAccount An ownCloud account; needed to start downloads\r
- */\r
- public FileDetailFragment(OCFile fileToDetail, Account ocAccount) {\r
- mFile = fileToDetail;\r
- mAccount = ocAccount;\r
- mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment \r
- mLayout = R.layout.file_details_empty;\r
- }\r
- \r
- \r
- @Override\r
- public void onCreate(Bundle savedInstanceState) {\r
- super.onCreate(savedInstanceState);\r
- mHandler = new Handler();\r
- }\r
- \r
- \r
- @Override\r
- public View onCreateView(LayoutInflater inflater, ViewGroup container,\r
- Bundle savedInstanceState) {\r
- super.onCreateView(inflater, container, savedInstanceState);\r
- \r
- if (savedInstanceState != null) {\r
- mFile = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_FILE);\r
- mAccount = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_ACCOUNT);\r
- }\r
- \r
- if(mFile != null && mAccount != null) {\r
- mLayout = R.layout.file_details_fragment;\r
- }\r
- \r
- View view = null;\r
- view = inflater.inflate(mLayout, container, false);\r
- mView = view;\r
- \r
- if (mLayout == R.layout.file_details_fragment) {\r
- mView.findViewById(R.id.fdKeepInSync).setOnClickListener(this);\r
- mView.findViewById(R.id.fdRenameBtn).setOnClickListener(this);\r
- mView.findViewById(R.id.fdDownloadBtn).setOnClickListener(this);\r
- mView.findViewById(R.id.fdOpenBtn).setOnClickListener(this);\r
- mView.findViewById(R.id.fdRemoveBtn).setOnClickListener(this);\r
- //mView.findViewById(R.id.fdShareBtn).setOnClickListener(this);\r
- mPreview = (ImageView)mView.findViewById(R.id.fdPreview);\r
- }\r
- \r
- updateFileDetails(false);\r
- return view;\r
- }\r
- \r
- \r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public void onAttach(Activity activity) {\r
- super.onAttach(activity);\r
- try {\r
- mContainerActivity = (ContainerActivity) activity;\r
- } catch (ClassCastException e) {\r
- throw new ClassCastException(activity.toString() + " must implement " + FileDetailFragment.ContainerActivity.class.getSimpleName());\r
- }\r
- }\r
- \r
- \r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public void onActivityCreated(Bundle savedInstanceState) {\r
- super.onActivityCreated(savedInstanceState);\r
- if (mAccount != null) {\r
- mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());;\r
- }\r
- }\r
- \r
- \r
- @Override\r
- public void onSaveInstanceState(Bundle outState) {\r
- Log.i(getClass().toString(), "onSaveInstanceState() start");\r
- super.onSaveInstanceState(outState);\r
- outState.putParcelable(FileDetailFragment.EXTRA_FILE, mFile);\r
- outState.putParcelable(FileDetailFragment.EXTRA_ACCOUNT, mAccount);\r
- Log.i(getClass().toString(), "onSaveInstanceState() end");\r
- }\r
- \r
- \r
- @Override\r
- public void onResume() {\r
- super.onResume();\r
- \r
- mDownloadFinishReceiver = new DownloadFinishReceiver();\r
- IntentFilter filter = new IntentFilter(\r
- FileDownloader.DOWNLOAD_FINISH_MESSAGE);\r
- getActivity().registerReceiver(mDownloadFinishReceiver, filter);\r
- \r
- mUploadFinishReceiver = new UploadFinishReceiver();\r
- filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE);\r
- getActivity().registerReceiver(mUploadFinishReceiver, filter);\r
- \r
- mPreview = (ImageView)mView.findViewById(R.id.fdPreview);\r
- }\r
- \r
- @Override\r
- public void onPause() {\r
- super.onPause();\r
- \r
- getActivity().unregisterReceiver(mDownloadFinishReceiver);\r
- mDownloadFinishReceiver = null;\r
- \r
- getActivity().unregisterReceiver(mUploadFinishReceiver);\r
- mUploadFinishReceiver = null;\r
- \r
- if (mPreview != null) {\r
- mPreview = null;\r
- }\r
- }\r
- \r
+ /* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+ package com.owncloud.android.ui.fragment;
+
+ import java.io.File;
+ import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.commons.httpclient.methods.GetMethod;
-import org.apache.commons.httpclient.methods.PostMethod;
-import org.apache.commons.httpclient.methods.StringRequestEntity;
-import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
-import org.apache.http.HttpStatus;
-import org.apache.http.NameValuePair;
-import org.apache.http.client.utils.URLEncodedUtils;
-import org.apache.http.message.BasicNameValuePair;
-import org.apache.http.protocol.HTTP;
-import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
-import org.json.JSONObject;
+
+ import android.accounts.Account;
-import android.accounts.AccountManager;
-//import android.annotation.SuppressLint;
+ import android.app.Activity;
+ import android.content.ActivityNotFoundException;
+ import android.content.BroadcastReceiver;
+ import android.content.Context;
+ import android.content.Intent;
+ import android.content.IntentFilter;
+ import android.net.Uri;
+ import android.os.Bundle;
+ import android.os.Handler;
+ import android.support.v4.app.FragmentTransaction;
-import android.util.Log;
+ import android.view.LayoutInflater;
+ import android.view.View;
+ import android.view.View.OnClickListener;
+ import android.view.ViewGroup;
+ import android.webkit.MimeTypeMap;
+ import android.widget.Button;
+ import android.widget.CheckBox;
+ import android.widget.ImageView;
+ import android.widget.ProgressBar;
+ import android.widget.TextView;
+ import android.widget.Toast;
+
-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.DialogFragment;
-import android.support.v4.app.FragmentTransaction;
-import android.util.Log;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.webkit.MimeTypeMap;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-
+ import com.actionbarsherlock.app.SherlockFragment;
-import com.owncloud.android.AccountUtils;
+ import com.owncloud.android.DisplayUtils;
+ import com.owncloud.android.Log_OC;
-import com.owncloud.android.authenticator.AccountAuthenticator;
+ 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.network.OwnCloudClientUtils;
+ import com.owncloud.android.operations.OnRemoteOperationListener;
+ import com.owncloud.android.operations.RemoteOperation;
+ import com.owncloud.android.operations.RemoteOperationResult;
+ import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
+ import com.owncloud.android.operations.RemoveFileOperation;
+ import com.owncloud.android.operations.RenameFileOperation;
+ import com.owncloud.android.operations.SynchronizeFileOperation;
+ import com.owncloud.android.ui.activity.ConflictsResolveActivity;
+ import com.owncloud.android.ui.activity.FileDetailActivity;
+ 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.utils.OwnCloudVersion;
+
+ import com.owncloud.android.R;
+
+ import eu.alefzero.webdav.OnDatatransferProgressListener;
-import eu.alefzero.webdav.WebdavClient;
+ import eu.alefzero.webdav.WebdavUtils;
+
-
+ /**
+ * This Fragment is used to display the details about a file.
+ *
+ * @author Bartek Przybylski
+ * @author David A. Velasco
+ */
+ public class FileDetailFragment extends SherlockFragment implements
+ OnClickListener,
+ ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener, EditNameDialogListener,
+ FileFragment {
+
+ public static final String EXTRA_FILE = "FILE";
+ public static final String EXTRA_ACCOUNT = "ACCOUNT";
+
+ private FileFragment.ContainerActivity mContainerActivity;
+
+ private int mLayout;
+ private View mView;
+ private OCFile mFile;
+ 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 = "FileDetails";
+ 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() {
+ mFile = null;
+ 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) {
+ mFile = 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();
+ }
+
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ super.onCreateView(inflater, container, savedInstanceState);
+
+ if (savedInstanceState != null) {
+ mFile = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_FILE);
+ mAccount = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_ACCOUNT);
+ }
+
+ if(mFile != null && mAccount != null) {
+ mLayout = R.layout.file_details_fragment;
+ }
+
+ View view = null;
+ view = inflater.inflate(mLayout, container, false);
+ mView = view;
+
+ if (mLayout == R.layout.file_details_fragment) {
+ mView.findViewById(R.id.fdKeepInSync).setOnClickListener(this);
+ mView.findViewById(R.id.fdRenameBtn).setOnClickListener(this);
+ mView.findViewById(R.id.fdDownloadBtn).setOnClickListener(this);
+ mView.findViewById(R.id.fdOpenBtn).setOnClickListener(this);
+ mView.findViewById(R.id.fdRemoveBtn).setOnClickListener(this);
+ //mView.findViewById(R.id.fdShareBtn).setOnClickListener(this);
+ ProgressBar progressBar = (ProgressBar)mView.findViewById(R.id.fdProgressBar);
+ mProgressListener = new ProgressListener(progressBar);
+ }
+
+ 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(FileDetailFragment.EXTRA_FILE, mFile);
+ outState.putParcelable(FileDetailFragment.EXTRA_ACCOUNT, mAccount);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ listenForTransferProgress();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mUploadFinishReceiver = new UploadFinishReceiver();
+ IntentFilter filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE);
+ 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();
- }
-
- @Override
- public void onClick(View v) {
- switch (v.getId()) {
- case R.id.fdDownloadBtn: {
- FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
- FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
- if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) {
- downloaderBinder.cancel(mAccount, mFile);
- if (mFile.isDown()) {
- setButtonsForDown();
- } else {
- setButtonsForRemote();
- }
-
- } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile)) {
- uploaderBinder.cancel(mAccount, mFile);
- if (!mFile.fileExists()) {
- // TODO make something better
- if (getActivity() instanceof FileDisplayActivity) {
- // double pane
- FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
- transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FTAG); // empty FileDetailFragment
- transaction.commit();
- mContainerActivity.onFileStateChanged();
- } else {
- getActivity().finish();
- }
-
- } else if (mFile.isDown()) {
- setButtonsForDown();
- } else {
- setButtonsForRemote();
- }
-
- } else {
- mLastRemoteOperation = new SynchronizeFileOperation(mFile, null, mStorageManager, mAccount, true, false, getActivity());
- WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());
- mLastRemoteOperation.execute(wc, this, mHandler);
-
- // update ui
- boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
- getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
-
- }
- break;
- }
+ @Override\r
+ public View getView() {\r
+ return super.getView() == null ? mView : super.getView();\r
+ }\r
- \r
- \r
++
+ \r
+ @Override\r
+ public void onClick(View v) {\r
+ switch (v.getId()) {\r
+ case R.id.fdDownloadBtn: {\r
+ FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();\r
+ FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();\r
+ if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) {\r
+ downloaderBinder.cancel(mAccount, mFile);\r
+ if (mFile.isDown()) {\r
+ setButtonsForDown();\r
+ } else {\r
+ setButtonsForRemote();\r
+ }\r
+\r
+ } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile)) {\r
+ uploaderBinder.cancel(mAccount, mFile);\r
+ if (!mFile.fileExists()) {\r
+ // TODO make something better\r
+ if (getActivity() instanceof FileDisplayActivity) {\r
+ // double pane\r
+ FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();\r
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FTAG); // empty FileDetailFragment\r
+ transaction.commit();\r
+ mContainerActivity.onFileStateChanged();\r
+ } else {\r
+ getActivity().finish();\r
+ }\r
+ \r
+ } else if (mFile.isDown()) {\r
+ setButtonsForDown();\r
+ } else {\r
+ setButtonsForRemote();\r
+ }\r
+ \r
+ } else {\r
+ mLastRemoteOperation = new SynchronizeFileOperation(mFile, null, mStorageManager, mAccount, true, false, getActivity());\r
+ mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity());\r
+ \r
+ // update ui \r
+ boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;\r
+ getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);\r
+ setButtonsForTransferring(); // disable button immediately, although the synchronization does not result in a file transference\r
+ \r
+ }\r
+ break;\r
+ }\r
- case R.id.fdKeepInSync: {\r
- CheckBox cb = (CheckBox) getView().findViewById(R.id.fdKeepInSync);\r
- mFile.setKeepInSync(cb.isChecked());\r
- mStorageManager.saveFile(mFile);\r
- \r
- /// register the OCFile instance in the observer service to monitor local updates;\r
- /// if necessary, the file is download \r
- Intent intent = new Intent(getActivity().getApplicationContext(),\r
- FileObserverService.class);\r
- intent.putExtra(FileObserverService.KEY_FILE_CMD,\r
- (cb.isChecked()?\r
- FileObserverService.CMD_ADD_OBSERVED_FILE:\r
- FileObserverService.CMD_DEL_OBSERVED_FILE));\r
- intent.putExtra(FileObserverService.KEY_CMD_ARG_FILE, mFile);\r
- intent.putExtra(FileObserverService.KEY_CMD_ARG_ACCOUNT, mAccount);\r
- Log.e(TAG, "starting observer service");\r
- getActivity().startService(intent);\r
- \r
- if (mFile.keepInSync()) {\r
- onClick(getView().findViewById(R.id.fdDownloadBtn)); // force an immediate synchronization\r
- }\r
- break;\r
- }\r
- case R.id.fdRenameBtn: {\r
- EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mFile.getFileName(), this);\r
- dialog.show(getFragmentManager(), "nameeditdialog");\r
- break;\r
- } \r
- case R.id.fdRemoveBtn: {\r
- ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(\r
- R.string.confirmation_remove_alert,\r
- new String[]{mFile.getFileName()},\r
- mFile.isDown() ? R.string.confirmation_remove_remote_and_local : R.string.confirmation_remove_remote,\r
- mFile.isDown() ? R.string.confirmation_remove_local : -1,\r
- R.string.common_cancel);\r
- confDialog.setOnConfirmationListener(this);\r
- mCurrentDialog = confDialog;\r
- mCurrentDialog.show(getFragmentManager(), FTAG_CONFIRMATION);\r
- break;\r
- }\r
- case R.id.fdOpenBtn: {\r
- String storagePath = mFile.getStoragePath();\r
- String encodedStoragePath = WebdavUtils.encodePath(storagePath);\r
- try {\r
- Intent i = new Intent(Intent.ACTION_VIEW);\r
- i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mFile.getMimetype());\r
- i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);\r
- startActivity(i);\r
- \r
- } catch (Throwable t) {\r
- Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype());\r
- boolean toastIt = true; \r
- String mimeType = "";\r
- try {\r
- Intent i = new Intent(Intent.ACTION_VIEW);\r
- mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));\r
- if (mimeType == null || !mimeType.equals(mFile.getMimetype())) {\r
- if (mimeType != null) {\r
- i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);\r
- } else {\r
- // desperate try\r
- i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*");\r
- }\r
- i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);\r
- startActivity(i);\r
- toastIt = false;\r
- }\r
- \r
- } catch (IndexOutOfBoundsException e) {\r
- Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);\r
- \r
- } catch (ActivityNotFoundException e) {\r
- Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");\r
- \r
- } catch (Throwable th) {\r
- Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);\r
- \r
- } finally {\r
- if (toastIt) {\r
- Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show();\r
- }\r
- }\r
- \r
- }\r
- break;\r
- }\r
- default:\r
- Log.e(TAG, "Incorrect view clicked!");\r
- }\r
- \r
- /* else if (v.getId() == R.id.fdShareBtn) {\r
- Thread t = new Thread(new ShareRunnable(mFile.getRemotePath()));\r
- t.start();\r
- }*/\r
- }\r
- \r
- \r
- @Override\r
- public void onConfirmation(String callerTag) {\r
- if (callerTag.equals(FTAG_CONFIRMATION)) {\r
- if (mStorageManager.getFileById(mFile.getFileId()) != null) {\r
- mLastRemoteOperation = new RemoveFileOperation( mFile, \r
- true, \r
- mStorageManager);\r
- mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity());\r
- boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;\r
- getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);\r
- }\r
- }\r
- mCurrentDialog.dismiss();\r
- mCurrentDialog = null;\r
- }\r
- \r
- @Override\r
- public void onNeutral(String callerTag) {\r
- File f = null;\r
- if (mFile.isDown() && (f = new File(mFile.getStoragePath())).exists()) {\r
- f.delete();\r
- mFile.setStoragePath(null);\r
- mStorageManager.saveFile(mFile);\r
- updateFileDetails(mFile, mAccount);\r
- }\r
- mCurrentDialog.dismiss();\r
- mCurrentDialog = null;\r
- }\r
- \r
- @Override\r
- public void onCancel(String callerTag) {\r
- Log.d(TAG, "REMOVAL CANCELED");\r
- mCurrentDialog.dismiss();\r
- mCurrentDialog = null;\r
- }\r
- \r
- \r
- /**\r
- * Check if the fragment was created with an empty layout. An empty fragment can't show file details, must be replaced.\r
- * \r
- * @return True when the fragment was created with the empty layout.\r
- */\r
- public boolean isEmpty() {\r
- return (mLayout == R.layout.file_details_empty || mFile == null || mAccount == null);\r
- }\r
- \r
- \r
- /**\r
- * Can be used to get the file that is currently being displayed.\r
- * @return The file on the screen.\r
- */\r
- public OCFile getDisplayedFile(){\r
- return mFile;\r
- }\r
- \r
- /**\r
- * Use this method to signal this Activity that it shall update its view.\r
- * \r
- * @param file : An {@link OCFile}\r
- */\r
- public void updateFileDetails(OCFile file, Account ocAccount) {\r
- mFile = file;\r
- if (ocAccount != null && ( \r
- mStorageManager == null || \r
- (mAccount != null && !mAccount.equals(ocAccount))\r
- )) {\r
- mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver());\r
- }\r
- mAccount = ocAccount;\r
- updateFileDetails(false);\r
- }\r
- \r
- \r
- /**\r
- * Updates the view with all relevant details about that file.\r
- *\r
- * TODO Remove parameter when the transferring state of files is kept in database. \r
- * \r
- * @param transferring Flag signaling if the file should be considered as downloading or uploading, \r
- * although {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and \r
- * {@link FileUploaderBinder#isUploading(Account, OCFile)} return false.\r
- * \r
- */\r
- public void updateFileDetails(boolean transferring) {\r
- \r
- if (mFile != null && mAccount != null && mLayout == R.layout.file_details_fragment) {\r
- \r
- // set file details\r
- setFilename(mFile.getFileName());\r
- setFiletype(mFile.getMimetype());\r
- setFilesize(mFile.getFileLength());\r
- if(ocVersionSupportsTimeCreated()){\r
- setTimeCreated(mFile.getCreationTimestamp());\r
- }\r
- \r
- setTimeModified(mFile.getModificationTimestamp());\r
- \r
- CheckBox cb = (CheckBox)getView().findViewById(R.id.fdKeepInSync);\r
- cb.setChecked(mFile.keepInSync());\r
- \r
- // configure UI for depending upon local state of the file\r
- //if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath()) || FileUploader.isUploading(mAccount, mFile.getRemotePath())) {\r
- FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();\r
- FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();\r
- if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile))) {\r
- setButtonsForTransferring();\r
- \r
- } else if (mFile.isDown()) {\r
- // Update preview\r
- if (mFile.getMimetype().startsWith("image/")) {\r
- BitmapLoader bl = new BitmapLoader();\r
- bl.execute(new String[]{mFile.getStoragePath()});\r
- }\r
- \r
- setButtonsForDown();\r
- \r
- } else {\r
- // TODO load default preview image; when the local file is removed, the preview remains there\r
- setButtonsForRemote();\r
- }\r
- }\r
- getView().invalidate();\r
- }\r
- \r
- \r
- /**\r
- * Updates the filename in view\r
- * @param filename to set\r
- */\r
- private void setFilename(String filename) {\r
- TextView tv = (TextView) getView().findViewById(R.id.fdFilename);\r
- if (tv != null)\r
- tv.setText(filename);\r
- }\r
- \r
- /**\r
- * Updates the MIME type in view\r
- * @param mimetype to set\r
- */\r
- private void setFiletype(String mimetype) {\r
- TextView tv = (TextView) getView().findViewById(R.id.fdType);\r
- if (tv != null) {\r
- String printableMimetype = DisplayUtils.convertMIMEtoPrettyPrint(mimetype);; \r
- tv.setText(printableMimetype);\r
- }\r
- ImageView iv = (ImageView) getView().findViewById(R.id.fdIcon);\r
- if (iv != null) {\r
- iv.setImageResource(DisplayUtils.getResourceId(mimetype));\r
- }\r
- }\r
- \r
- /**\r
- * Updates the file size in view\r
- * @param filesize in bytes to set\r
- */\r
- private void setFilesize(long filesize) {\r
- TextView tv = (TextView) getView().findViewById(R.id.fdSize);\r
- if (tv != null)\r
- tv.setText(DisplayUtils.bytesToHumanReadable(filesize));\r
- }\r
- \r
- /**\r
- * Updates the time that the file was created in view\r
- * @param milliseconds Unix time to set\r
- */\r
- private void setTimeCreated(long milliseconds){\r
- TextView tv = (TextView) getView().findViewById(R.id.fdCreated);\r
- TextView tvLabel = (TextView) getView().findViewById(R.id.fdCreatedLabel);\r
- if(tv != null){\r
- tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds));\r
- tv.setVisibility(View.VISIBLE);\r
- tvLabel.setVisibility(View.VISIBLE);\r
- }\r
- }\r
- \r
- /**\r
- * Updates the time that the file was last modified\r
- * @param milliseconds Unix time to set\r
- */\r
- private void setTimeModified(long milliseconds){\r
- TextView tv = (TextView) getView().findViewById(R.id.fdModified);\r
- if(tv != null){\r
- tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds));\r
- }\r
- }\r
- \r
- /**\r
- * Enables or disables buttons for a file being downloaded\r
- */\r
- private void setButtonsForTransferring() {\r
- if (!isEmpty()) {\r
- Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn);\r
- downloadButton.setText(R.string.common_cancel);\r
- //downloadButton.setEnabled(false);\r
- \r
- // let's protect the user from himself ;)\r
- ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(false);\r
- ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(false);\r
- ((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(false);\r
- getView().findViewById(R.id.fdKeepInSync).setEnabled(false);\r
- }\r
- }\r
- \r
- /**\r
- * Enables or disables buttons for a file locally available \r
- */\r
- private void setButtonsForDown() {\r
- if (!isEmpty()) {\r
- Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn);\r
- downloadButton.setText(R.string.filedetails_sync_file);\r
- \r
- ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(true);\r
- ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true);\r
- ((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(true);\r
- getView().findViewById(R.id.fdKeepInSync).setEnabled(true);\r
- }\r
- }\r
- \r
- /**\r
- * Enables or disables buttons for a file not locally available \r
- */\r
- private void setButtonsForRemote() {\r
- if (!isEmpty()) {\r
- Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn);\r
- downloadButton.setText(R.string.filedetails_download);\r
- \r
- ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(false);\r
- ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true);\r
- ((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(true);\r
- getView().findViewById(R.id.fdKeepInSync).setEnabled(true);\r
- }\r
- }\r
- \r
- \r
- /**\r
- * In ownCloud 3.X.X and 4.X.X there is a bug that SabreDAV does not return\r
- * the time that the file was created. There is a chance that this will\r
- * be fixed in future versions. Use this method to check if this version of\r
- * ownCloud has this fix.\r
- * @return True, if ownCloud the ownCloud version is supporting creation time\r
- */\r
- private boolean ocVersionSupportsTimeCreated(){\r
- /*if(mAccount != null){\r
- AccountManager accManager = (AccountManager) getActivity().getSystemService(Context.ACCOUNT_SERVICE);\r
- OwnCloudVersion ocVersion = new OwnCloudVersion(accManager\r
- .getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION));\r
- if(ocVersion.compareTo(new OwnCloudVersion(0x030000)) < 0) {\r
- return true;\r
- }\r
- }*/\r
- return false;\r
- }\r
- \r
- \r
- /**\r
- * Interface to implement by any Activity that includes some instance of FileDetailFragment\r
- * \r
- * @author David A. Velasco\r
- */\r
- public interface ContainerActivity extends TransferServiceGetter {\r
- \r
- /**\r
- * Callback method invoked when the detail fragment wants to notice its container \r
- * activity about a relevant state the file shown by the fragment.\r
- * \r
- * Added to notify to FileDisplayActivity about the need of refresh the files list. \r
- * \r
- * Currently called when:\r
- * - a download is started;\r
- * - a rename is completed;\r
- * - a deletion is completed;\r
- * - the 'inSync' flag is changed;\r
- */\r
- public void onFileStateChanged();\r
- \r
- }\r
- \r
- \r
- /**\r
- * Once the file download has finished -> update view\r
- * @author Bartek Przybylski\r
- */\r
- private class DownloadFinishReceiver extends BroadcastReceiver {\r
- @Override\r
- public void onReceive(Context context, Intent intent) {\r
- String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);\r
- \r
- if (!isEmpty() && accountName.equals(mAccount.name)) {\r
- boolean downloadWasFine = intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false);\r
- String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);\r
- if (mFile.getRemotePath().equals(downloadedRemotePath)) {\r
- if (downloadWasFine) {\r
- mFile = mStorageManager.getFileByPath(downloadedRemotePath);\r
- }\r
- updateFileDetails(false); // it updates the buttons; must be called although !downloadWasFine\r
- }\r
- }\r
- }\r
- }\r
- \r
- \r
- /**\r
- * Once the file upload has finished -> update view\r
- * \r
- * Being notified about the finish of an upload is necessary for the next sequence:\r
- * 1. Upload a big file.\r
- * 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\r
- * of its containing folder; the the server includes it in the PROPFIND requests although it's not fully upload. \r
- * 3. Click the file in the list to see its details.\r
- * 4. Wait for the upload finishes; at this moment, the details view must be refreshed to enable the action buttons.\r
- */\r
- private class UploadFinishReceiver extends BroadcastReceiver {\r
- @Override\r
- public void onReceive(Context context, Intent intent) {\r
- String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME);\r
- \r
- if (!isEmpty() && accountName.equals(mAccount.name)) {\r
- boolean uploadWasFine = intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false);\r
- String uploadRemotePath = intent.getStringExtra(FileUploader.EXTRA_REMOTE_PATH);\r
- boolean renamedInUpload = mFile.getRemotePath().equals(intent.getStringExtra(FileUploader.EXTRA_OLD_REMOTE_PATH));\r
- if (mFile.getRemotePath().equals(uploadRemotePath) ||\r
- renamedInUpload) {\r
- if (uploadWasFine) {\r
- mFile = mStorageManager.getFileByPath(uploadRemotePath);\r
- }\r
- if (renamedInUpload) {\r
- String newName = (new File(uploadRemotePath)).getName();\r
- Toast msg = Toast.makeText(getActivity().getApplicationContext(), String.format(getString(R.string.filedetails_renamed_in_upload_msg), newName), Toast.LENGTH_LONG);\r
- msg.show();\r
- }\r
- getSherlockActivity().removeStickyBroadcast(intent); // not the best place to do this; a small refactorization of BroadcastReceivers should be done\r
- updateFileDetails(false); // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server\r
- }\r
- }\r
- }\r
- }\r
- \r
- \r
- // this is a temporary class for sharing purposes, it need to be replaced in transfer service\r
- /*\r
- @SuppressWarnings("unused")\r
- private class ShareRunnable implements Runnable {\r
- private String mPath;\r
- \r
- public ShareRunnable(String path) {\r
- mPath = path;\r
- }\r
- \r
- public void run() {\r
- AccountManager am = AccountManager.get(getActivity());\r
- Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());\r
- OwnCloudVersion ocv = new OwnCloudVersion(am.getUserData(account, AccountAuthenticator.KEY_OC_VERSION));\r
- String url = am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + AccountUtils.getWebdavPath(ocv);\r
- \r
- Log.d("share", "sharing for version " + ocv.toString());\r
- \r
- if (ocv.compareTo(new OwnCloudVersion(0x040000)) >= 0) {\r
- String APPS_PATH = "/apps/files_sharing/";\r
- String SHARE_PATH = "ajax/share.php";\r
- \r
- String SHARED_PATH = "/apps/files_sharing/get.php?token=";\r
- \r
- final String WEBDAV_SCRIPT = "webdav.php";\r
- final String WEBDAV_FILES_LOCATION = "/files/";\r
- \r
- WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(account, getActivity().getApplicationContext());\r
- HttpConnectionManagerParams params = new HttpConnectionManagerParams();\r
- params.setMaxConnectionsPerHost(wc.getHostConfiguration(), 5);\r
- \r
- //wc.getParams().setParameter("http.protocol.single-cookie-header", true);\r
- //wc.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);\r
- \r
- PostMethod post = new PostMethod(am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + APPS_PATH + SHARE_PATH);\r
- \r
- post.addRequestHeader("Content-type","application/x-www-form-urlencoded; charset=UTF-8" );\r
- post.addRequestHeader("Referer", am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL));\r
- List<NameValuePair> formparams = new ArrayList<NameValuePair>();\r
- Log.d("share", mPath+"");\r
- formparams.add(new BasicNameValuePair("sources",mPath));\r
- formparams.add(new BasicNameValuePair("uid_shared_with", "public"));\r
- formparams.add(new BasicNameValuePair("permissions", "0"));\r
- post.setRequestEntity(new StringRequestEntity(URLEncodedUtils.format(formparams, HTTP.UTF_8)));\r
- \r
- int status;\r
- try {\r
- PropFindMethod find = new PropFindMethod(url+"/");\r
- find.addRequestHeader("Referer", am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL));\r
- Log.d("sharer", ""+ url+"/");\r
- \r
- for (org.apache.commons.httpclient.Header a : find.getRequestHeaders()) {\r
- Log.d("sharer-h", a.getName() + ":"+a.getValue());\r
- }\r
- \r
- int status2 = wc.executeMethod(find);\r
- \r
- Log.d("sharer", "propstatus "+status2);\r
- \r
- GetMethod get = new GetMethod(am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + "/");\r
- get.addRequestHeader("Referer", am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL));\r
- \r
- status2 = wc.executeMethod(get);\r
- \r
- Log.d("sharer", "getstatus "+status2);\r
- Log.d("sharer", "" + get.getResponseBodyAsString());\r
- \r
- for (org.apache.commons.httpclient.Header a : get.getResponseHeaders()) {\r
- Log.d("sharer", a.getName() + ":"+a.getValue());\r
- }\r
- \r
- status = wc.executeMethod(post);\r
- for (org.apache.commons.httpclient.Header a : post.getRequestHeaders()) {\r
- Log.d("sharer-h", a.getName() + ":"+a.getValue());\r
- }\r
- for (org.apache.commons.httpclient.Header a : post.getResponseHeaders()) {\r
- Log.d("sharer", a.getName() + ":"+a.getValue());\r
- }\r
- String resp = post.getResponseBodyAsString();\r
- Log.d("share", ""+post.getURI().toString());\r
- Log.d("share", "returned status " + status);\r
- Log.d("share", " " +resp);\r
- \r
- if(status != HttpStatus.SC_OK ||resp == null || resp.equals("") || resp.startsWith("false")) {\r
- return;\r
- }\r
- \r
- JSONObject jsonObject = new JSONObject (resp);\r
- String jsonStatus = jsonObject.getString("status");\r
- if(!jsonStatus.equals("success")) throw new Exception("Error while sharing file status != success");\r
- \r
- String token = jsonObject.getString("data");\r
- String uri = am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + SHARED_PATH + token; \r
- Log.d("Actions:shareFile ok", "url: " + uri); \r
- \r
- } catch (Exception e) {\r
- e.printStackTrace();\r
- }\r
- \r
- } else if (ocv.compareTo(new OwnCloudVersion(0x030000)) >= 0) {\r
- \r
- }\r
- }\r
- }\r
- */\r
- \r
- public void onDismiss(EditNameDialog dialog) {\r
- if (dialog.getResult()) {\r
- String newFilename = dialog.getNewFilename();\r
- Log.d(TAG, "name edit dialog dismissed with new name " + newFilename);\r
- mLastRemoteOperation = new RenameFileOperation( mFile, \r
- mAccount, \r
- newFilename, \r
- new FileDataStorageManager(mAccount, getActivity().getContentResolver()));\r
- mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity());\r
- boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;\r
- getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);\r
- }\r
- }\r
- \r
- \r
- class BitmapLoader extends AsyncTask<String, Void, Bitmap> {\r
- @SuppressLint({ "NewApi", "NewApi", "NewApi" }) // to avoid Lint errors since Android SDK r20\r
- @Override\r
- protected Bitmap doInBackground(String... params) {\r
- Bitmap result = null;\r
- if (params.length != 1) return result;\r
- String storagePath = params[0];\r
- try {\r
- \r
- BitmapFactory.Options options = new Options();\r
- options.inScaled = true;\r
- options.inPurgeable = true;\r
- options.inJustDecodeBounds = true;\r
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {\r
- options.inPreferQualityOverSpeed = false;\r
- }\r
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {\r
- options.inMutable = false;\r
- }\r
- \r
- result = BitmapFactory.decodeFile(storagePath, options);\r
- options.inJustDecodeBounds = false;\r
- \r
- int width = options.outWidth;\r
- int height = options.outHeight;\r
- int scale = 1;\r
- if (width >= 2048 || height >= 2048) {\r
- scale = (int) Math.ceil((Math.ceil(Math.max(height, width) / 2048.)));\r
- options.inSampleSize = scale;\r
- }\r
- Display display = getActivity().getWindowManager().getDefaultDisplay();\r
- Point size = new Point();\r
- int screenwidth;\r
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR2) {\r
- display.getSize(size);\r
- screenwidth = size.x;\r
- } else {\r
- screenwidth = display.getWidth();\r
- }\r
- \r
- Log.e("ASD", "W " + width + " SW " + screenwidth);\r
- \r
- if (width > screenwidth) {\r
- scale = (int) Math.ceil((float)width / screenwidth);\r
- options.inSampleSize = scale;\r
- }\r
- \r
- result = BitmapFactory.decodeFile(storagePath, options);\r
- \r
- Log.e("ASD", "W " + options.outWidth + " SW " + options.outHeight);\r
- \r
- } catch (OutOfMemoryError e) {\r
- result = null;\r
- Log.e(TAG, "Out of memory occured for file with size " + storagePath);\r
- \r
- } catch (NoSuchFieldError e) {\r
- result = null;\r
- Log.e(TAG, "Error from access to unexisting field despite protection " + storagePath);\r
- \r
- } catch (Throwable t) {\r
- result = null;\r
- Log.e(TAG, "Unexpected error while creating image preview " + storagePath, t);\r
- }\r
- return result;\r
- }\r
- @Override\r
- protected void onPostExecute(Bitmap result) {\r
- if (result != null && mPreview != null) {\r
- mPreview.setImageBitmap(result);\r
- }\r
- }\r
- \r
- }\r
- \r
- /**\r
- * {@inheritDoc}\r
- */\r
- @Override\r
- public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {\r
- if (operation instanceof RemoveFileOperation) {\r
- onRemoveFileOperationFinish((RemoveFileOperation)operation, result);\r
- \r
- } else if (operation instanceof RenameFileOperation) {\r
- onRenameFileOperationFinish((RenameFileOperation)operation, result);\r
- \r
- } else if (operation instanceof SynchronizeFileOperation) {\r
- onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result);\r
- }\r
- }\r
- \r
- \r
- private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) {\r
- boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;\r
- getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);\r
- \r
- if (result.isSuccess()) {\r
- Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG);\r
- msg.show();\r
- if (inDisplayActivity) {\r
- // double pane\r
- FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();\r
- transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null)); // empty FileDetailFragment\r
- transaction.commit();\r
- mContainerActivity.onFileStateChanged();\r
- } else {\r
- getActivity().finish();\r
- }\r
- \r
- } else {\r
- Toast msg = Toast.makeText(getActivity(), R.string.remove_fail_msg, Toast.LENGTH_LONG); \r
- msg.show();\r
- if (result.isSslRecoverableException()) {\r
- // TODO show the SSL warning dialog\r
- }\r
- }\r
- }\r
- \r
- private void onRenameFileOperationFinish(RenameFileOperation operation, RemoteOperationResult result) {\r
- boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;\r
- getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);\r
- \r
- if (result.isSuccess()) {\r
- updateFileDetails(((RenameFileOperation)operation).getFile(), mAccount);\r
- mContainerActivity.onFileStateChanged();\r
- \r
- } else {\r
- if (result.getCode().equals(ResultCode.INVALID_LOCAL_FILE_NAME)) {\r
- Toast msg = Toast.makeText(getActivity(), R.string.rename_local_fail_msg, Toast.LENGTH_LONG); \r
- msg.show();\r
- // TODO throw again the new rename dialog\r
- } else {\r
- Toast msg = Toast.makeText(getActivity(), R.string.rename_server_fail_msg, Toast.LENGTH_LONG); \r
- msg.show();\r
- if (result.isSslRecoverableException()) {\r
- // TODO show the SSL warning dialog\r
- }\r
- }\r
- }\r
- }\r
- \r
- private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) {\r
- boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;\r
- getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);\r
- \r
- if (!result.isSuccess()) {\r
- if (result.getCode() == ResultCode.SYNC_CONFLICT) {\r
- Intent i = new Intent(getActivity(), ConflictsResolveActivity.class);\r
- i.putExtra(ConflictsResolveActivity.EXTRA_FILE, mFile);\r
- i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mAccount);\r
- startActivity(i);\r
- \r
- } else {\r
- Toast msg = Toast.makeText(getActivity(), R.string.sync_file_fail_msg, Toast.LENGTH_LONG); \r
- msg.show();\r
- }\r
- \r
- if (mFile.isDown()) {\r
- setButtonsForDown();\r
- \r
- } else {\r
- setButtonsForRemote();\r
- }\r
- \r
- } else {\r
- if (operation.transferWasRequested()) {\r
- mContainerActivity.onFileStateChanged(); // this is not working; FileDownloader won't do NOTHING at all until this method finishes, so \r
- // checking the service to see if the file is downloading results in FALSE\r
- } else {\r
- Toast msg = Toast.makeText(getActivity(), R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG); \r
- msg.show();\r
- if (mFile.isDown()) {\r
- setButtonsForDown();\r
- \r
- } else {\r
- setButtonsForRemote();\r
- }\r
- }\r
- }\r
- }\r
- \r
- }\r
+ case R.id.fdKeepInSync: {
+ CheckBox cb = (CheckBox) getView().findViewById(R.id.fdKeepInSync);
+ mFile.setKeepInSync(cb.isChecked());
+ mStorageManager.saveFile(mFile);
+
+ /// 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, mFile);
+ intent.putExtra(FileObserverService.KEY_CMD_ARG_ACCOUNT, mAccount);
+ getActivity().startService(intent);
+
+ if (mFile.keepInSync()) {
+ onClick(getView().findViewById(R.id.fdDownloadBtn)); // force an immediate synchronization
+ }
+ break;
+ }
+ case R.id.fdRenameBtn: {
+ String fileName = mFile.getFileName();
+ int extensionStart = mFile.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");
+ break;
+ }
+ case R.id.fdRemoveBtn: {
+ ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
+ R.string.confirmation_remove_alert,
+ new String[]{mFile.getFileName()},
+ mFile.isDown() ? R.string.confirmation_remove_remote_and_local : R.string.confirmation_remove_remote,
+ mFile.isDown() ? R.string.confirmation_remove_local : -1,
+ R.string.common_cancel);
+ confDialog.setOnConfirmationListener(this);
+ confDialog.show(getFragmentManager(), FTAG_CONFIRMATION);
+ break;
+ }
+ case R.id.fdOpenBtn: {
+ openFile();
+ break;
+ }
+ default:
+ Log_OC.e(TAG, "Incorrect view clicked!");
+ }
+
- /* else if (v.getId() == R.id.fdShareBtn) {
- Thread t = new Thread(new ShareRunnable(mFile.getRemotePath()));
- t.start();
- }*/
- }
-
-
++ }\r
++ \r
+ /**
+ * Opens mFile.
+ */
+ private void openFile() {
+
+ String storagePath = mFile.getStoragePath();
+ String encodedStoragePath = WebdavUtils.encodePath(storagePath);
+ try {
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mFile.getMimetype());
+ i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ startActivity(i);
+
+ } catch (Throwable t) {
- Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype());
++ Log_OC.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.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(mFile.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.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
++ Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
+
+ } catch (ActivityNotFoundException e) {
- Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
++ Log_OC.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
+
+ } catch (Throwable th) {
- Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
++ Log_OC.e(TAG, "Unexpected problem when opening: " + storagePath, th);
+
+ } finally {
+ if (toastIt) {
+ Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ }
+ }
+
-
+ @Override
+ public void onConfirmation(String callerTag) {
+ if (callerTag.equals(FTAG_CONFIRMATION)) {
+ if (mStorageManager.getFileById(mFile.getFileId()) != null) {
+ mLastRemoteOperation = new RemoveFileOperation( mFile,
+ true,
+ mStorageManager);
- WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());
- mLastRemoteOperation.execute(wc, this, mHandler);
++ mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity());
+
+ boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
+ getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
+ }
+ }
+ }
+
+ @Override
+ public void onNeutral(String callerTag) {
+ File f = null;
+ if (mFile.isDown() && (f = new File(mFile.getStoragePath())).exists()) {
+ f.delete();
+ mFile.setStoragePath(null);
+ mStorageManager.saveFile(mFile);
+ updateFileDetails(mFile, mAccount);
+ }
+ }
+
+ @Override
+ public void onCancel(String callerTag) {
- Log.d(TAG, "REMOVAL CANCELED");
++ 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 || mFile == null || mAccount == null);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public OCFile getFile(){
+ return mFile;
+ }
+
+ /**
+ * 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) {
+ mFile = 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) {
+ mFile = mStorageManager.getFileByPath(mFile.getRemotePath());
+ }
+
+ // set file details
+ setFilename(mFile.getFileName());
+ setFiletype(mFile.getMimetype());
+ setFilesize(mFile.getFileLength());
+ if(ocVersionSupportsTimeCreated()){
+ setTimeCreated(mFile.getCreationTimestamp());
+ }
+
+ setTimeModified(mFile.getModificationTimestamp());
+
+ CheckBox cb = (CheckBox)getView().findViewById(R.id.fdKeepInSync);
+ cb.setChecked(mFile.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, mFile)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile))) {
+ setButtonsForTransferring();
+
+ } else if (mFile.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 (mFile != 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()) {
+ Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn);
+ downloadButton.setText(R.string.common_cancel);
+ //downloadButton.setEnabled(false);
+
+ // let's protect the user from himself ;)
+ ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(false);
+ ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(false);
+ ((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(false);
+ getView().findViewById(R.id.fdKeepInSync).setEnabled(false);
+
+ // show the progress bar for the transfer
+ ProgressBar progressBar = (ProgressBar)getView().findViewById(R.id.fdProgressBar);
+ progressBar.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, mFile)) {
+ progressText.setText(R.string.downloader_download_in_progress_ticker);
+ } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile)) {
+ progressText.setText(R.string.uploader_upload_in_progress_ticker);
+ }
+ }
+ }
+
+ /**
+ * Enables or disables buttons for a file locally available
+ */
+ private void setButtonsForDown() {
+ if (!isEmpty()) {
+ Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn);
+ downloadButton.setText(R.string.filedetails_sync_file);
+
+ ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(true);
+ ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true);
+ ((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(true);
+ getView().findViewById(R.id.fdKeepInSync).setEnabled(true);
+
+ // hides the progress bar
+ ProgressBar progressBar = (ProgressBar)getView().findViewById(R.id.fdProgressBar);
+ progressBar.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()) {
+ Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn);
+ downloadButton.setText(R.string.filedetails_download);
+
+ ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(false);
+ ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true);
+ ((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(true);
+ getView().findViewById(R.id.fdKeepInSync).setEnabled(true);
+
+ // hides the progress bar
+ ProgressBar progressBar = (ProgressBar)getView().findViewById(R.id.fdProgressBar);
+ progressBar.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 = mFile.getRemotePath().equals(intent.getStringExtra(FileUploader.EXTRA_OLD_REMOTE_PATH));
+ if (mFile.getRemotePath().equals(uploadRemotePath) ||
+ renamedInUpload) {
+ if (uploadWasFine) {
+ mFile = 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
+ }
+ }
+ }
+ }
+
+
- // this is a temporary class for sharing purposes, it need to be replaced in transfer service
- @SuppressWarnings("unused")
- private class ShareRunnable implements Runnable {
- private String mPath;
-
- public ShareRunnable(String path) {
- mPath = path;
- }
-
- public void run() {
- AccountManager am = AccountManager.get(getActivity());
- Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
- OwnCloudVersion ocv = new OwnCloudVersion(am.getUserData(account, AccountAuthenticator.KEY_OC_VERSION));
- String url = am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + AccountUtils.getWebdavPath(ocv);
-
- Log.d("share", "sharing for version " + ocv.toString());
-
- if (ocv.compareTo(new OwnCloudVersion(0x040000)) >= 0) {
- String APPS_PATH = "/apps/files_sharing/";
- String SHARE_PATH = "ajax/share.php";
-
- String SHARED_PATH = "/apps/files_sharing/get.php?token=";
-
- final String WEBDAV_SCRIPT = "webdav.php";
- final String WEBDAV_FILES_LOCATION = "/files/";
-
- WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(account, getActivity().getApplicationContext());
- HttpConnectionManagerParams params = new HttpConnectionManagerParams();
- params.setMaxConnectionsPerHost(wc.getHostConfiguration(), 5);
-
- //wc.getParams().setParameter("http.protocol.single-cookie-header", true);
- //wc.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
-
- PostMethod post = new PostMethod(am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + APPS_PATH + SHARE_PATH);
-
- post.addRequestHeader("Content-type","application/x-www-form-urlencoded; charset=UTF-8" );
- post.addRequestHeader("Referer", am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL));
- List<NameValuePair> formparams = new ArrayList<NameValuePair>();
- Log.d("share", mPath+"");
- formparams.add(new BasicNameValuePair("sources",mPath));
- formparams.add(new BasicNameValuePair("uid_shared_with", "public"));
- formparams.add(new BasicNameValuePair("permissions", "0"));
- post.setRequestEntity(new StringRequestEntity(URLEncodedUtils.format(formparams, HTTP.UTF_8)));
-
- int status;
- try {
- PropFindMethod find = new PropFindMethod(url+"/");
- find.addRequestHeader("Referer", am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL));
- Log.d("sharer", ""+ url+"/");
-
- for (org.apache.commons.httpclient.Header a : find.getRequestHeaders()) {
- Log.d("sharer-h", a.getName() + ":"+a.getValue());
- }
-
- int status2 = wc.executeMethod(find);
-
- Log.d("sharer", "propstatus "+status2);
-
- GetMethod get = new GetMethod(am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + "/");
- get.addRequestHeader("Referer", am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL));
-
- status2 = wc.executeMethod(get);
-
- Log.d("sharer", "getstatus "+status2);
- Log.d("sharer", "" + get.getResponseBodyAsString());
-
- for (org.apache.commons.httpclient.Header a : get.getResponseHeaders()) {
- Log.d("sharer", a.getName() + ":"+a.getValue());
- }
-
- status = wc.executeMethod(post);
- for (org.apache.commons.httpclient.Header a : post.getRequestHeaders()) {
- Log.d("sharer-h", a.getName() + ":"+a.getValue());
- }
- for (org.apache.commons.httpclient.Header a : post.getResponseHeaders()) {
- Log.d("sharer", a.getName() + ":"+a.getValue());
- }
- String resp = post.getResponseBodyAsString();
- Log.d("share", ""+post.getURI().toString());
- Log.d("share", "returned status " + status);
- Log.d("share", " " +resp);
-
- if(status != HttpStatus.SC_OK ||resp == null || resp.equals("") || resp.startsWith("false")) {
- return;
- }
-
- JSONObject jsonObject = new JSONObject (resp);
- String jsonStatus = jsonObject.getString("status");
- if(!jsonStatus.equals("success")) throw new Exception("Error while sharing file status != success");
-
- String token = jsonObject.getString("data");
- String uri = am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + SHARED_PATH + token;
- Log.d("Actions:shareFile ok", "url: " + uri);
-
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- } else if (ocv.compareTo(new OwnCloudVersion(0x030000)) >= 0) {
-
- }
- }
- }
-
+ public void onDismiss(EditNameDialog dialog) {
+ if (dialog.getResult()) {
+ String newFilename = dialog.getNewFilename();
- Log.d(TAG, "name edit dialog dismissed with new name " + newFilename);
++ Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename);
+ mLastRemoteOperation = new RenameFileOperation( mFile,
+ mAccount,
+ newFilename,
+ new FileDataStorageManager(mAccount, getActivity().getContentResolver()));
- WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());
- mLastRemoteOperation.execute(wc, this, mHandler);
++ mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity());
+ boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
+ getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
+ }
+ }
+
+
+ /**
+ * {@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) {
+ boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
+ getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
+
+ if (result.isSuccess()) {
+ Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG);
+ msg.show();
+ if (inDisplayActivity) {
+ // double pane
+ FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null)); // empty FileDetailFragment
+ transaction.commit();
+ mContainerActivity.onFileStateChanged();
+ } else {
+ getActivity().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 onRenameFileOperationFinish(RenameFileOperation operation, RemoteOperationResult result) {
+ boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
+ getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
+
+ 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) {
+ boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
+ getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
+
+ if (!result.isSuccess()) {
+ if (result.getCode() == ResultCode.SYNC_CONFLICT) {
+ Intent i = new Intent(getActivity(), ConflictsResolveActivity.class);
+ i.putExtra(ConflictsResolveActivity.EXTRA_FILE, mFile);
+ 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 (mFile.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 (mFile.isDown()) {
+ setButtonsForDown();
+
+ } else {
+ setButtonsForRemote();
+ }
+ }
+ }
+ }
+
-
++
+ public void listenForTransferProgress() {
+ if (mProgressListener != null) {
+ if (mContainerActivity.getFileDownloaderBinder() != null) {
+ mContainerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, mFile);
+ }
+ if (mContainerActivity.getFileUploaderBinder() != null) {
+ mContainerActivity.getFileUploaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, mFile);
+ }
+ }
+ }
+
+
+ public void leaveTransferProgress() {
+ if (mProgressListener != null) {
+ if (mContainerActivity.getFileDownloaderBinder() != null) {
+ mContainerActivity.getFileDownloaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, mFile);
+ }
+ if (mContainerActivity.getFileUploaderBinder() != null) {
+ mContainerActivity.getFileUploaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, mFile);
+ }
+ }
+ }
+
+
+
+ /**
+ * 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<ProgressBar> mProgressBar = null;
+
+ ProgressListener(ProgressBar progressBar) {
+ mProgressBar = new WeakReference<ProgressBar>(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;
+ }
+
+ };
+
-}
++ /*
++ // this is a temporary class for sharing purposes, it need to be replaced in transfer service
++ @SuppressWarnings("unused")
++ private class ShareRunnable implements Runnable {
++ private String mPath;
++
++ public ShareRunnable(String path) {
++ mPath = path;
++ }
++
++ public void run() {
++ AccountManager am = AccountManager.get(getActivity());
++ Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
++ OwnCloudVersion ocv = new OwnCloudVersion(am.getUserData(account, AccountAuthenticator.KEY_OC_VERSION));
++ String url = am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + AccountUtils.getWebdavPath(ocv);
++
++ Log_OC.d("share", "sharing for version " + ocv.toString());
++
++ if (ocv.compareTo(new OwnCloudVersion(0x040000)) >= 0) {
++ String APPS_PATH = "/apps/files_sharing/";
++ String SHARE_PATH = "ajax/share.php";
++
++ String SHARED_PATH = "/apps/files_sharing/get.php?token=";
++
++ final String WEBDAV_SCRIPT = "webdav.php";
++ final String WEBDAV_FILES_LOCATION = "/files/";
++
++ WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(account, getActivity().getApplicationContext());
++ HttpConnectionManagerParams params = new HttpConnectionManagerParams();
++ params.setMaxConnectionsPerHost(wc.getHostConfiguration(), 5);
++
++ //wc.getParams().setParameter("http.protocol.single-cookie-header", true);
++ //wc.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
++
++ PostMethod post = new PostMethod(am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + APPS_PATH + SHARE_PATH);
++
++ post.addRequestHeader("Content-type","application/x-www-form-urlencoded; charset=UTF-8" );
++ post.addRequestHeader("Referer", am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL));
++ List<NameValuePair> formparams = new ArrayList<NameValuePair>();
++ Log_OC.d("share", mPath+"");
++ formparams.add(new BasicNameValuePair("sources",mPath));
++ formparams.add(new BasicNameValuePair("uid_shared_with", "public"));
++ formparams.add(new BasicNameValuePair("permissions", "0"));
++ post.setRequestEntity(new StringRequestEntity(URLEncodedUtils.format(formparams, HTTP.UTF_8)));
++
++ int status;
++ try {
++ PropFindMethod find = new PropFindMethod(url+"/");
++ find.addRequestHeader("Referer", am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL));
++ Log_OC.d("sharer", ""+ url+"/");
++
++ for (org.apache.commons.httpclient.Header a : find.getRequestHeaders()) {
++ Log_OC.d("sharer-h", a.getName() + ":"+a.getValue());
++ }
++
++ int status2 = wc.executeMethod(find);
++
++ Log_OC.d("sharer", "propstatus "+status2);
++
++ GetMethod get = new GetMethod(am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + "/");
++ get.addRequestHeader("Referer", am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL));
++
++ status2 = wc.executeMethod(get);
++
++ Log_OC.d("sharer", "getstatus "+status2);
++ Log_OC.d("sharer", "" + get.getResponseBodyAsString());
++
++ for (org.apache.commons.httpclient.Header a : get.getResponseHeaders()) {
++ Log_OC.d("sharer", a.getName() + ":"+a.getValue());
++ }
++
++ status = wc.executeMethod(post);
++ for (org.apache.commons.httpclient.Header a : post.getRequestHeaders()) {
++ Log_OC.d("sharer-h", a.getName() + ":"+a.getValue());
++ }
++ for (org.apache.commons.httpclient.Header a : post.getResponseHeaders()) {
++ Log_OC.d("sharer", a.getName() + ":"+a.getValue());
++ }
++ String resp = post.getResponseBodyAsString();
++ Log_OC.d("share", ""+post.getURI().toString());
++ Log_OC.d("share", "returned status " + status);
++ Log_OC.d("share", " " +resp);
++
++ if(status != HttpStatus.SC_OK ||resp == null || resp.equals("") || resp.startsWith("false")) {
++ return;
++ }
++
++ JSONObject jsonObject = new JSONObject (resp);
++ String jsonStatus = jsonObject.getString("status");
++ if(!jsonStatus.equals("success")) throw new Exception("Error while sharing file status != success");
++
++ String token = jsonObject.getString("data");
++ String uri = am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + SHARED_PATH + token;
++ Log_OC.d("Actions:shareFile ok", "url: " + uri);
++
++ } catch (Exception e) {
++ e.printStackTrace();
++ }
++
++ } else if (ocv.compareTo(new OwnCloudVersion(0x030000)) >= 0) {
++
++ }
++ }
++ }
++ */
++
++}
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
--import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
- import android.support.v4.app.DialogFragment;
--import android.util.Log;
import android.view.ContextMenu;
import android.view.MenuInflater;
import android.view.MenuItem;
}
return true;
}
- case R.id.download_file_item: {
+ case R.id.action_download_file: {
Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, false, getSherlockActivity());
- WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(account, getSherlockActivity().getApplicationContext());
- operation.execute(wc, mContainerActivity, mHandler);
+ operation.execute(account, getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
getSherlockActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
return true;
}
--- /dev/null
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License.
+ /* ownCloud Android client application
+ *
+ * Copyright (C) 2012-2013 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
-import android.util.Log;
++ * it under the terms of the GNU General Public License version 2,
++ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+ package com.owncloud.android.ui.preview;
+
+ import java.lang.ref.WeakReference;
+
+ import android.accounts.Account;
+ import android.app.Activity;
+ import android.os.Bundle;
+ import android.support.v4.app.FragmentStatePagerAdapter;
- Log.e(TAG, "Incorrect view clicked!");
+ import android.view.LayoutInflater;
+ import android.view.View;
+ import android.view.View.OnClickListener;
+ import android.view.ViewGroup;
+ import android.widget.Button;
+ import android.widget.ProgressBar;
+ import android.widget.TextView;
+
+ import com.actionbarsherlock.app.SherlockFragment;
+ import com.owncloud.android.datamodel.FileDataStorageManager;
+ import com.owncloud.android.datamodel.OCFile;
+ import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
+ import com.owncloud.android.ui.fragment.FileFragment;
+
++import com.owncloud.android.Log_OC;
+ import com.owncloud.android.R;
+
+ 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 SherlockFragment implements OnClickListener, FileFragment {
+
+ 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 OCFile mFile;
+ private Account mAccount;
+ private FileDataStorageManager mStorageManager;
+
+ 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() {
+ mFile = null;
+ mAccount = null;
+ mStorageManager = 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) {
+ mFile = fileToDetail;
+ mAccount = ocAccount;
+ mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment
+ 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) {
+ mFile = 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);
+
+ ((Button)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, mFile);
+ 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, mFile)) {
+ downloaderBinder.cancel(mAccount, mFile);
+ getActivity().finish(); // :)
+ /*
+ leaveTransferProgress();
+ if (mFile.isDown()) {
+ setButtonsForDown();
+ } else {
+ setButtonsForRemote();
+ }
+ */
+ }
+ break;
+ }
+ default:
++ Log_OC.e(TAG, "Incorrect view clicked!");
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public OCFile getFile(){
+ return mFile;
+ }
+
+
+ /**
+ * 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, mFile))) {
+ setButtonsForTransferring();
+
+ } else if (mFile.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, mFile);
+ mListening = true;
+ setButtonsForTransferring();
+ }
+ }
+ }
+
+
+ public void leaveTransferProgress() {
+ if (mProgressListener != null) {
+ if (mContainerActivity.getFileDownloaderBinder() != null) {
+ mContainerActivity.getFileDownloaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, mFile);
+ 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<ProgressBar> mProgressBar = null;
+
+ ProgressListener(ProgressBar progressBar) {
+ mProgressBar = new WeakReference<ProgressBar>(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;
+ };
+
+
+
+ }
--- /dev/null
-import org.apache.commons.httpclient.methods.PostMethod;
-
+ /* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+ package com.owncloud.android.ui.preview;
+
-import android.support.v4.app.Fragment;
+ import android.accounts.Account;
+ import android.app.Dialog;
+ import android.app.ProgressDialog;
+ 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.util.Log;
+ import android.support.v4.view.ViewPager;
- Log.d(TAG, "Simulating reselection of current page after connection of download binder");
+ import android.view.MotionEvent;
+ import android.view.View;
+ import android.view.View.OnTouchListener;
+
+ import com.actionbarsherlock.app.ActionBar;
+ import com.actionbarsherlock.app.SherlockFragmentActivity;
+ import com.actionbarsherlock.view.MenuItem;
+ import com.actionbarsherlock.view.Window;
+ 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.FileDownloader.FileDownloaderBinder;
+ import com.owncloud.android.files.services.FileUploader;
+ import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+ import com.owncloud.android.ui.activity.FileDetailActivity;
+ import com.owncloud.android.ui.fragment.FileDetailFragment;
+ import com.owncloud.android.ui.fragment.FileFragment;
+
+ import com.owncloud.android.AccountUtils;
++import com.owncloud.android.Log_OC;
+ import com.owncloud.android.R;
+
+ /**
+ * Used as an utility to preview image files contained in an ownCloud account.
+ *
+ * @author David A. Velasco
+ */
+ public class PreviewImageActivity extends SherlockFragmentActivity 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 OCFile mFile;
+ private OCFile mParentFolder;
+ private Account mAccount;
+ 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;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mFile = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE);
+ mAccount = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT);
+ if (mFile == null) {
+ throw new IllegalStateException("Instanced with a NULL OCFile");
+ }
+ if (mAccount == null) {
+ throw new IllegalStateException("Instanced with a NULL ownCloud Account");
+ }
+ if (!mFile.isImage()) {
+ throw new IllegalArgumentException("Non-image file passed as argument");
+ }
+ requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
+ setContentView(R.layout.preview_image_activity);
+
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setTitle(mFile.getFileName());
+ actionBar.hide();
+
+ mFullScreen = true;
+
+ mStorageManager = new FileDataStorageManager(mAccount, getContentResolver());
+ mParentFolder = mStorageManager.getFileById(mFile.getParentId());
+ if (mParentFolder == null) {
+ // should not be necessary
+ mParentFolder = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR);
+ }
+
+ if (savedInstanceState != null) {
+ mRequestWaitingForBinder = savedInstanceState.getBoolean(KEY_WAITING_FOR_BINDER);
+ } else {
+ mRequestWaitingForBinder = false;
+ }
+
+ createViewPager();
+
+ }
+
+ private void createViewPager() {
+ mPreviewImagePagerAdapter = new PreviewImagePagerAdapter(getSupportFragmentManager(), mParentFolder, mAccount, mStorageManager);
+ mViewPager = (ViewPager) findViewById(R.id.fragmentPager);
+ int position = mPreviewImagePagerAdapter.getFilePosition(mFile);
+ position = (position >= 0) ? position : 0;
+ mViewPager.setAdapter(mPreviewImagePagerAdapter);
+ mViewPager.setOnPageChangeListener(this);
+ mViewPager.setCurrentItem(position);
+ if (position == 0 && !mFile.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.d(TAG, "Upload service connected");
++ 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.d(TAG, "Download service suddenly disconnected");
++ 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.d(TAG, "Upload service suddenly disconnected");
++ Log_OC.d(TAG, "Download service suddenly disconnected");
+ mDownloaderBinder = null;
+ } else if (component.equals(new ComponentName(PreviewImageActivity.this, FileUploader.class))) {
- Log.d(TAG, "requestForDownload called without binder to download service");
++ 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(FileDownloader.DOWNLOAD_FINISH_MESSAGE);
+ filter.addAction(FileDownloader.DOWNLOAD_ADDED_MESSAGE);
+ 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() {
+ /*
+ Intent intent = new Intent(this, FileDisplayActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.putExtra(FileDetailFragment.EXTRA_FILE, mFile);
+ intent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, mAccount);
+ startActivity(intent);
+ */
+ finish();
+ }
+
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ Dialog dialog = null;
+ 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;
+ }
+ default:
+ dialog = null;
+ }
+ return dialog;
+ }
+
+
+ /**
+ * {@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 showFragmentWithDetails(OCFile file) {
+ Intent showDetailsIntent = new Intent(this, FileDetailActivity.class);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file);
+ showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));
+ showDetailsIntent.putExtra(FileDetailActivity.EXTRA_MODE, FileDetailActivity.MODE_DETAILS);
+ startActivity(showDetailsIntent);
+ int pos = mPreviewImagePagerAdapter.getFilePosition(file);
+ file = mPreviewImagePagerAdapter.getFileAt(pos);
+
+ }
+
+
+ private void requestForDownload(OCFile file) {
+ if (mDownloaderBinder == null) {
- Log.d(TAG, "Download finished, but the fragment is offscreen");
++ Log_OC.d(TAG, "requestForDownload called without binder to download service");
+
+ } else if (!mDownloaderBinder.isDownloading(mAccount, file)) {
+ Intent i = new Intent(this, FileDownloader.class);
+ i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
+ 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 (mAccount.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(FileDownloader.DOWNLOAD_FINISH_MESSAGE)) {
+ 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;
+ }
+
+
+ }
--- /dev/null
-import android.util.Log;
+ /* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+ 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 com.owncloud.android.network.OwnCloudClientUtils;
+ 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.app.SherlockFragment;
+ import com.actionbarsherlock.view.Menu;
+ import com.actionbarsherlock.view.MenuInflater;
+ import com.actionbarsherlock.view.MenuItem;
+ import com.owncloud.android.datamodel.FileDataStorageManager;
+ import com.owncloud.android.datamodel.OCFile;
-import eu.alefzero.webdav.WebdavClient;
+ 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 com.owncloud.android.Log_OC;
+ import com.owncloud.android.R;
- Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype());
+ 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 SherlockFragment implements FileFragment,
+ OnRemoteOperationListener,
+ ConfirmationDialogFragment.ConfirmationDialogFragmentListener {
+ public static final String EXTRA_FILE = "FILE";
+ public static final String EXTRA_ACCOUNT = "ACCOUNT";
+
+ private View mView;
+ private OCFile mFile;
+ 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) {
+ mFile = 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() {
+ mFile = null;
+ 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) {
+ mFile = savedInstanceState.getParcelable(PreviewImageFragment.EXTRA_FILE);
+ mAccount = savedInstanceState.getParcelable(PreviewImageFragment.EXTRA_ACCOUNT);
+ } else {
+ mIgnoreFirstSavedState = false;
+ }
+ }
+ if (mFile == null) {
+ throw new IllegalStateException("Instanced with a NULL OCFile");
+ }
+ if (mAccount == null) {
+ throw new IllegalStateException("Instanced with a NULL ownCloud Account");
+ }
+ if (!mFile.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, mFile);
+ outState.putParcelable(PreviewImageFragment.EXTRA_ACCOUNT, mAccount);
+ }
+
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (mFile != null) {
+ BitmapLoader bl = new BitmapLoader(mImageView, mMessageView, mProgressWheel);
+ bl.execute(new String[]{mFile.getStoragePath()});
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+
+ inflater.inflate(R.menu.file_actions_menu, menu);
+ List<Integer> toHide = new ArrayList<Integer>();
+
+ 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()).showFragmentWithDetails(mFile);
+ }
+
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ //Log.e(TAG, "FRAGMENT, ONRESUME");
+ /*
+ mDownloadFinishReceiver = new DownloadFinishReceiver();
+ IntentFilter filter = new IntentFilter(
+ FileDownloader.DOWNLOAD_FINISH_MESSAGE);
+ getActivity().registerReceiver(mDownloadFinishReceiver, filter);
+
+ mUploadFinishReceiver = new UploadFinishReceiver();
+ filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE);
+ getActivity().registerReceiver(mUploadFinishReceiver, filter);
+ */
+
+ }
+
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ /*
+ if (mVideoPreview.getVisibility() == View.VISIBLE) {
+ mSavedPlaybackPosition = mVideoPreview.getCurrentPosition();
+ }*/
+ /*
+ getActivity().unregisterReceiver(mDownloadFinishReceiver);
+ mDownloadFinishReceiver = null;
+
+ getActivity().unregisterReceiver(mUploadFinishReceiver);
+ mUploadFinishReceiver = null;
+ */
+ }
+
+
+ @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() {
+ String storagePath = mFile.getStoragePath();
+ String encodedStoragePath = WebdavUtils.encodePath(storagePath);
+ try {
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mFile.getMimetype());
+ i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ startActivity(i);
+
+ } catch (Throwable t) {
- Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
++ Log_OC.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.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(mFile.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.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
++ Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
+
+ } catch (ActivityNotFoundException e) {
- Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
++ Log_OC.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
+
+ } catch (Throwable th) {
- WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());
- mLastRemoteOperation.execute(wc, this, mHandler);
++ Log_OC.e(TAG, "Unexpected problem when opening: " + storagePath, th);
+
+ } finally {
+ if (toastIt) {
+ Toast.makeText(getActivity(), "There is no application to handle file " + mFile.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[]{mFile.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(mFile.getFileId()) != null) { // check that the file is still there;
+ mLastRemoteOperation = new RemoveFileOperation( mFile, // TODO we need to review the interface with RemoteOperations, and use OCFile IDs instead of OCFile objects as parameters
+ true,
+ mStorageManager);
- //Log.d(TAG, "Image loaded - width: " + options.outWidth + ", loaded height: " + options.outHeight);
++ mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity());
+
+ getActivity().showDialog(PreviewImageActivity.DIALOG_SHORT_WAIT);
+ }
+ }
+
+
+ /**
+ * Removes the file from local storage
+ */
+ @Override
+ public void onNeutral(String callerTag) {
+ // TODO this code should be made in a secondary thread,
+ if (mFile.isDown()) { // checks it is still there
+ File f = new File(mFile.getStoragePath());
+ f.delete();
+ mFile.setStoragePath(null);
+ mStorageManager.saveFile(mFile);
+ finish();
+ }
+ }
+
+ /**
+ * User cancelled the removal action.
+ */
+ @Override
+ public void onCancel(String callerTag) {
+ // nothing to do here
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public OCFile getFile(){
+ return mFile;
+ }
+
+ /*
+ /**
+ * 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) {
+ mFile = file;
+ if (ocAccount != null && (
+ mStorageManager == null ||
+ (mAccount != null && !mAccount.equals(ocAccount))
+ )) {
+ mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver());
+ }
+ mAccount = ocAccount;
+ updateFileDetails(false);
+ }
+ */
+
+
+ private class BitmapLoader extends AsyncTask<String, Void, Bitmap> {
+
+ /**
+ * 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<ImageView> 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<TextView> 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<ProgressBar> 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>(imageView);
+ mMessageViewRef = new WeakReference<TextView>(messageView);
+ mProgressWheelRef = new WeakReference<ProgressBar>(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.e(TAG, "File could not be loaded as a bitmap: " + storagePath);
++ //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.e(TAG, "Out of memory occured for file " + storagePath, e);
++ 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.e(TAG, "Error from access to unexisting field despite protection; file " + storagePath, e);
++ Log_OC.e(TAG, "Out of memory occured for file " + storagePath, e);
+
+ } catch (NoSuchFieldError e) {
+ mErrorMessageId = R.string.common_error_unknown;
- Log.e(TAG, "Unexpected error loading " + mFile.getStoragePath(), t);
++ 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 " + mFile.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) {
+ getActivity().dismissDialog(PreviewImageActivity.DIALOG_SHORT_WAIT);
+
+ 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();
+ }
+
+
+ }
--- /dev/null
-import java.util.ArrayList;
+ /* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+ package com.owncloud.android.ui.preview;
+
-import android.os.Bundle;
-import android.os.Parcelable;
+ 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.FragmentTransaction;
-import android.support.v4.view.PagerAdapter;
-import android.support.v4.view.ViewPager;
-import android.util.Log;
-import android.view.View;
+ import android.support.v4.app.Fragment;
+ import android.support.v4.app.FragmentManager;
+ import android.support.v4.app.FragmentStatePagerAdapter;
+ import android.view.ViewGroup;
+
+ import com.owncloud.android.datamodel.DataStorageManager;
+ import com.owncloud.android.datamodel.OCFile;
+ import com.owncloud.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 static final String TAG = PreviewImagePagerAdapter.class.getSimpleName();
+
+ private Vector<OCFile> mImageFiles;
+ private Account mAccount;
+ private Set<Object> mObsoleteFragments;
+ private Set<Integer> mObsoletePositions;
+ private Set<Integer> mDownloadErrors;
+ private DataStorageManager mStorageManager;
+
+ private Map<Integer, FileFragment> mCachedFragments;
+
+ /*
+ private final FragmentManager mFragmentManager;
+ private FragmentTransaction mCurTransaction = null;
+ private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
+ private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
+ private Fragment mCurrentPrimaryItem = null;
+ */
+
+ /**
+ * 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<Object>();
+ mObsoletePositions = new HashSet<Integer>();
+ mDownloadErrors = new HashSet<Integer>();
+ //mFragmentManager = fragmentManager;
+ mCachedFragments = new HashMap<Integer, FileFragment>();
+ }
+
+
+ /**
+ * 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<mFragments.size(); i++) {
+ Fragment fragment = mFragments.get(i);
+ if (fragment != null) {
+ if (state == null) {
+ state = new Bundle();
+ }
+ String key = "f" + i;
+ mFragmentManager.putFragment(state, key, fragment);
+ }
+ }
+ return state;
+ }
+
+ @Override
+ public void restoreState(Parcelable state, ClassLoader loader) {
+ if (state != null) {
+ Bundle bundle = (Bundle)state;
+ bundle.setClassLoader(loader);
+ Parcelable[] states = bundle.getParcelableArray("states");
+ mSavedState.clear();
+ mFragments.clear();
+ if (states != null) {
+ for (int i=0; i<states.length; i++) {
+ mSavedState.add((Fragment.SavedState)states[i]);
+ }
+ }
+ Iterable<String> 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);
+ }
+ }
+ }
+ }
+ }
+ */
+ }
--- /dev/null
-import android.util.Log;
+ /* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+ 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.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.support.v4.app.FragmentTransaction;
-import com.owncloud.android.network.OwnCloudClientUtils;
+ 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.app.SherlockFragment;
+ import com.actionbarsherlock.view.Menu;
+ import com.actionbarsherlock.view.MenuInflater;
+ import com.actionbarsherlock.view.MenuItem;
+ 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 eu.alefzero.webdav.WebdavClient;
+ 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.FileDetailActivity;
+ import com.owncloud.android.ui.activity.FileDisplayActivity;
+ import com.owncloud.android.ui.fragment.ConfirmationDialogFragment;
+ import com.owncloud.android.ui.fragment.FileDetailFragment;
+ import com.owncloud.android.ui.fragment.FileFragment;
+
++import com.owncloud.android.Log_OC;
+ import com.owncloud.android.R;
- Log.e(TAG, "onPrepared");
+ 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 SherlockFragment implements
+ OnTouchListener , FileFragment,
+ 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 OCFile mFile;
+ 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;
+
+ 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) {
+ mFile = fileToDetail;
+ mAccount = ocAccount;
+ mSavedPlaybackPosition = 0;
+ mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment
+ mAutoplay = true;
+ }
+
+
+ /**
+ * 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() {
+ mFile = null;
+ 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);
+
+ 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);
+ 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) {
+ mFile = savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_FILE);
+ mAccount = savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_ACCOUNT);
+ mSavedPlaybackPosition = savedInstanceState.getInt(PreviewMediaFragment.EXTRA_PLAY_POSITION);
+ mAutoplay = savedInstanceState.getBoolean(PreviewMediaFragment.EXTRA_PLAYING);
+
+ }
+ if (mFile == null) {
+ throw new IllegalStateException("Instanced with a NULL OCFile");
+ }
+ if (mAccount == null) {
+ throw new IllegalStateException("Instanced with a NULL ownCloud Account");
+ }
+ if (!mFile.isDown()) {
+ throw new IllegalStateException("There is no local file to preview");
+ }
+ if (mFile.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);
+ outState.putParcelable(PreviewMediaFragment.EXTRA_FILE, mFile);
+ outState.putParcelable(PreviewMediaFragment.EXTRA_ACCOUNT, mAccount);
+
+ if (mFile.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();
+
+ if (mFile != null) {
+ if (mFile.isAudio()) {
+ bindMediaService();
+
+ } else if (mFile.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<Integer> toHide = new ArrayList<Integer>();
+
+ 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() {
+ stopPreview(false);
+ ((FileFragment.ContainerActivity)getActivity()).showFragmentWithDetails(mFile);
+ }
+
+
+ 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(mFile.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.e(TAG, "completed");
++ Log_OC.e(TAG, "onPrepared");
+ mVideoPreview.seekTo(mSavedPlaybackPosition);
+ if (mAutoplay) {
+ mVideoPreview.start();
+ }
+ mMediaController.setEnabled(true);
+ mMediaController.updatePausePlay();
+ }
+
+
+ /**
+ * 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.d(TAG, "Unbinding from MediaService ...");
++ 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(mFile.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 onStop() {
+ super.onStop();
+
+ if (mMediaServiceConnection != null) {
- Log.d(TAG, "starting playback of " + mFile.getStoragePath());
++ 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(PreviewVideoActivity.EXTRA_ACCOUNT, mAccount);
+ i.putExtra(PreviewVideoActivity.EXTRA_FILE, mFile);
+ i.putExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, mVideoPreview.isPlaying());
+ mVideoPreview.pause();
+ i.putExtra(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPreview.getCurrentPosition());
+ startActivityForResult(i, 0);
+ }
+
+
+ @Override
+ public void onActivityResult (int requestCode, int resultCode, Intent data) {
+ 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() {
+ if (!mMediaServiceBinder.isPlaying(mFile)) {
- Log.d(TAG, "Binding to MediaService...");
++ Log_OC.d(TAG, "starting playback of " + mFile.getStoragePath());
+ mMediaServiceBinder.start(mAccount, mFile, mAutoplay, mSavedPlaybackPosition);
+
+ } else {
+ if (!mMediaServiceBinder.isPlaying() && mAutoplay) {
+ mMediaServiceBinder.start();
+ mMediaController.updatePausePlay();
+ }
+ }
+ }
+
+
+ private void bindMediaService() {
- Log.d(TAG, "Media service connected");
++ 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.d(TAG, "Successfully bound to MediaService, MediaController ready");
++ 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.e(TAG, "Unexpected response from MediaService while binding");
++ Log_OC.d(TAG, "Successfully bound to MediaService, MediaController ready");
+
+ } else {
- Log.e(TAG, "Media service suddenly disconnected");
++ 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.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype());
++ 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() {
+ stopPreview(true);
+ String storagePath = mFile.getStoragePath();
+ String encodedStoragePath = WebdavUtils.encodePath(storagePath);
+ try {
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mFile.getMimetype());
+ i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ startActivity(i);
+
+ } catch (Throwable t) {
- Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
++ Log_OC.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.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(mFile.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.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
++ Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
+
+ } catch (ActivityNotFoundException e) {
- Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
++ Log_OC.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
+
+ } catch (Throwable th) {
- WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());
- mLastRemoteOperation.execute(wc, this, mHandler);
++ Log_OC.e(TAG, "Unexpected problem when opening: " + storagePath, th);
+
+ } finally {
+ if (toastIt) {
+ Toast.makeText(getActivity(), "There is no application to handle file " + mFile.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[]{mFile.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(mFile.getFileId()) != null) { // check that the file is still there;
+ stopPreview(true);
+ mLastRemoteOperation = new RemoveFileOperation( mFile, // 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());
+
+ boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
+ getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
+ }
+ }
+
+
+ /**
+ * Removes the file from local storage
+ */
+ @Override
+ public void onNeutral(String callerTag) {
+ // TODO this code should be made in a secondary thread,
+ if (mFile.isDown()) { // checks it is still there
+ stopPreview(true);
+ File f = new File(mFile.getStoragePath());
+ f.delete();
+ mFile.setStoragePath(null);
+ mStorageManager.saveFile(mFile);
+ finish();
+ }
+ }
+
+ /**
+ * User cancelled the removal action.
+ */
+ @Override
+ public void onCancel(String callerTag) {
+ // nothing to do here
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public OCFile getFile(){
+ return mFile;
+ }
+
+ /*
+ /**
+ * 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) {
+ mFile = file;
+ if (ocAccount != null && (
+ mStorageManager == null ||
+ (mAccount != null && !mAccount.equals(ocAccount))
+ )) {
+ mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver());
+ }
+ mAccount = ocAccount;
+ updateFileDetails(false);
+ }
+ */
+
+
+ /**
+ * 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) {
+ boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
+ getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
+
+ 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) {
+ if (mFile.isAudio() && stopAudio) {
+ mMediaServiceBinder.pause();
+
+ } else if (mFile.isVideo()) {
+ mVideoPreview.stopPlayback();
+ }
+ }
+
+
+
+ /**
+ * Finishes the preview
+ */
+ private void finish() {
+ Activity container = getActivity();
+ if (container instanceof FileDisplayActivity) {
+ // double pane
+ FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment
+ transaction.commit();
+ ((FileFragment.ContainerActivity)container).onFileStateChanged();
+ } else {
+ container.finish();
+ }
+ }
+
+ }
--- /dev/null
-import android.util.Log;
+ /* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+ package com.owncloud.android.ui.preview;
+
+ import android.accounts.Account;
+ import android.app.Activity;
+ 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;
- Log.e(TAG, "ACTIVITY\t\tonCreate");
+ import android.view.MotionEvent;
+ import android.widget.MediaController;
+ import android.widget.VideoView;
+
+ import com.owncloud.android.AccountUtils;
++import com.owncloud.android.Log_OC;
+ import com.owncloud.android.R;
+ import com.owncloud.android.datamodel.OCFile;
+ import com.owncloud.android.media.MediaService;
+
+ /**
+ * 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 Activity implements OnCompletionListener, OnPreparedListener, OnErrorListener {
+
+ /** Key to receive an {@link OCFile} to play as an extra value in an {@link Intent} */
+ public static final String EXTRA_FILE = "FILE";
+
+ /** Key to receive the ownCloud {@link Account} where the file to play is saved as an extra value in an {@link Intent} */
+ public static final String EXTRA_ACCOUNT = "ACCOUNT";
+
+ /** 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 OCFile mFile; // video file to play
+ private Account mAccount; // ownCloud account holding mFile
+ 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.e(TAG, "ACTIVITY\t\tonSaveInstanceState");
++ Log_OC.e(TAG, "ACTIVITY\t\tonCreate");
+
+ setContentView(R.layout.video_layout);
+
+ if (savedInstanceState == null) {
+ Bundle extras = getIntent().getExtras();
+ mFile = extras.getParcelable(EXTRA_FILE);
+ mAccount = extras.getParcelable(EXTRA_ACCOUNT);
+ mSavedPlaybackPosition = extras.getInt(EXTRA_START_POSITION);
+ mAutoplay = extras.getBoolean(EXTRA_AUTOPLAY);
+
+ } else {
+ mFile = savedInstanceState.getParcelable(EXTRA_FILE);
+ mAccount = savedInstanceState.getParcelable(EXTRA_ACCOUNT);
+ 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);
+
+ if (mFile != null) {
+ if (mFile.isDown()) {
+ mVideoPlayer.setVideoPath(mFile.getStoragePath());
+
+ } else if (mAccount != null) {
+ // not working now
+ String url = AccountUtils.constructFullURLForAccount(this, mAccount) + mFile.getRemotePath();
+ mVideoPlayer.setVideoURI(Uri.parse(url));
+
+ } else {
+ 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 {
+ onError(null, MediaService.OC_MEDIA_ERROR, R.string.media_err_nothing_to_play);
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
- Log.e(TAG, "ACTIVTIY\t\tonBackPressed");
++ Log_OC.e(TAG, "ACTIVITY\t\tonSaveInstanceState");
+ outState.putParcelable(PreviewVideoActivity.EXTRA_FILE, mFile);
+ outState.putParcelable(PreviewVideoActivity.EXTRA_ACCOUNT, mAccount);
+ outState.putInt(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPlayer.getCurrentPosition());
+ outState.putBoolean(PreviewVideoActivity.EXTRA_AUTOPLAY , mVideoPlayer.isPlaying());
+ }
+
+
+ @Override
+ public void onBackPressed() {
- Log.e(TAG, "ACTIVTIY\t\tonResume");
++ 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();
+ }
+
+
+ @Override
+ public void onResume() {
+ super.onResume();
- Log.e(TAG, "ACTIVTIY\t\tonStart");
++ Log_OC.e(TAG, "ACTIVTIY\t\tonResume");
+ }
+
+
+ @Override
+ public void onStart() {
+ super.onStart();
- Log.e(TAG, "ACTIVITY\t\tonDestroy");
++ Log_OC.e(TAG, "ACTIVTIY\t\tonStart");
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
- Log.e(TAG, "ACTIVTIY\t\tonStop");
++ Log_OC.e(TAG, "ACTIVITY\t\tonDestroy");
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
- Log.e(TAG, "ACTIVTIY\t\tonPause");
++ Log_OC.e(TAG, "ACTIVTIY\t\tonStop");
+ }
+
+
+ @Override
+ public void onPause() {
+ super.onPause();
- Log.e(TAG, "ACTIVITY\t\tonPrepare");
++ Log_OC.e(TAG, "ACTIVTIY\t\tonPause");
+ }
+
+
+ /**
+ * 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.e(TAG, "Error in video playback, what = " + what + ", extra = " + extra);
++ 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;
+ }
+
+
+ /**
+ * Screen touches trigger the appearance of the control panel for a limited time.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onTouchEvent (MotionEvent ev){
+ /*if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ if (mMediaController.isShowing()) {
+ mMediaController.hide();
+ } else {
+ mMediaController.show(MediaService.MEDIA_CONTROL_SHORT_LIFE);
+ }
+ return true;
+ } else {
+ return false;
+ }*/
+ return false;
+ }
+
+
+ }
import android.net.Uri;
import android.os.Environment;
import android.os.StatFs;
-import android.util.Log;
+ import com.owncloud.android.R;
import com.owncloud.android.datamodel.OCFile;
-
/**
* Static methods to help in access to local file system.
*
- /* ownCloud Android client application\r
- * Copyright (C) 2011 Bartek Przybylski\r
- * Copyright (C) 2012-2013 ownCloud Inc.\r
- *\r
- * This program is free software: you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 2 of the License, or\r
- * (at your option) any later version.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program. If not, see <http://www.gnu.org/licenses/>.\r
- *\r
- */\r
- package eu.alefzero.webdav;\r
- \r
- import java.io.BufferedInputStream;\r
- import java.io.File;\r
- import java.io.FileOutputStream;\r
- import java.io.IOException;\r
- import java.io.InputStream;\r
- import java.util.ArrayList;\r
- import java.util.List;\r
- \r
- import org.apache.commons.httpclient.Credentials;\r
- import org.apache.commons.httpclient.HostConfiguration;\r
- import org.apache.commons.httpclient.HttpClient;\r
- import org.apache.commons.httpclient.HttpConnectionManager;\r
- import org.apache.commons.httpclient.HttpException;\r
- import org.apache.commons.httpclient.HttpMethod;\r
- import org.apache.commons.httpclient.HttpMethodBase;\r
- import org.apache.commons.httpclient.HttpState;\r
- import org.apache.commons.httpclient.HttpVersion;\r
- import org.apache.commons.httpclient.UsernamePasswordCredentials;\r
- import org.apache.commons.httpclient.auth.AuthPolicy;\r
- import org.apache.commons.httpclient.auth.AuthScope;\r
- import org.apache.commons.httpclient.methods.GetMethod;\r
- import org.apache.commons.httpclient.methods.HeadMethod;\r
- import org.apache.commons.httpclient.methods.PutMethod;\r
- import org.apache.commons.httpclient.params.HttpMethodParams;\r
- import org.apache.http.HttpStatus;\r
- import org.apache.http.params.CoreProtocolPNames;\r
- import org.apache.jackrabbit.webdav.client.methods.DavMethod;\r
- import org.apache.jackrabbit.webdav.client.methods.DeleteMethod;\r
- \r
- import com.owncloud.android.network.BearerAuthScheme;\r
- import com.owncloud.android.network.BearerCredentials;\r
- \r
- import android.net.Uri;\r
- import android.util.Log;\r
- \r
- public class WebdavClient extends HttpClient {\r
- private Uri mUri;\r
- private Credentials mCredentials;\r
- final private static String TAG = "WebdavClient";\r
- private static final String USER_AGENT = "Android-ownCloud";\r
- \r
- private OnDatatransferProgressListener mDataTransferListener;\r
- static private byte[] sExhaustBuffer = new byte[1024];\r
- \r
- /**\r
- * Constructor\r
- */\r
- public WebdavClient(HttpConnectionManager connectionMgr) {\r
- super(connectionMgr);\r
- Log.d(TAG, "Creating WebdavClient");\r
- getParams().setParameter(HttpMethodParams.USER_AGENT, USER_AGENT);\r
- getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);\r
- }\r
- \r
- public void setBearerCredentials(String accessToken) {\r
- AuthPolicy.registerAuthScheme(BearerAuthScheme.AUTH_POLICY, BearerAuthScheme.class);\r
- \r
- List<String> authPrefs = new ArrayList<String>(1);\r
- authPrefs.add(BearerAuthScheme.AUTH_POLICY);\r
- getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs); \r
- \r
- mCredentials = new BearerCredentials(accessToken);\r
- getState().setCredentials(AuthScope.ANY, mCredentials);\r
- }\r
- \r
- public void setBasicCredentials(String username, String password) {\r
- List<String> authPrefs = new ArrayList<String>(1);\r
- authPrefs.add(AuthPolicy.BASIC);\r
- getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs); \r
- \r
- getParams().setAuthenticationPreemptive(true);\r
- mCredentials = new UsernamePasswordCredentials(username, password);\r
- getState().setCredentials(AuthScope.ANY, mCredentials);\r
- }\r
- \r
- \r
- /**\r
- * Downloads a file in remoteFilepath to the local targetPath.\r
- * \r
- * @param remoteFilepath Path to the file in the remote server, URL DECODED. \r
- * @param targetFile Local path to save the downloaded file.\r
- * @return 'True' when the file is successfully downloaded.\r
- */\r
- public boolean downloadFile(String remoteFilePath, File targetFile) {\r
- boolean ret = false;\r
- GetMethod get = new GetMethod(mUri.toString() + WebdavUtils.encodePath(remoteFilePath));\r
- \r
- try {\r
- int status = executeMethod(get);\r
- if (status == HttpStatus.SC_OK) {\r
- targetFile.createNewFile();\r
- BufferedInputStream bis = new BufferedInputStream(\r
- get.getResponseBodyAsStream());\r
- FileOutputStream fos = new FileOutputStream(targetFile);\r
- \r
- byte[] bytes = new byte[4096];\r
- int readResult;\r
- while ((readResult = bis.read(bytes)) != -1) {\r
- if (mDataTransferListener != null)\r
- mDataTransferListener.onTransferProgress(readResult);\r
- fos.write(bytes, 0, readResult);\r
- }\r
- fos.close();\r
- ret = true;\r
- } else {\r
- exhaustResponse(get.getResponseBodyAsStream());\r
- }\r
- Log.e(TAG, "Download of " + remoteFilePath + " to " + targetFile + " finished with HTTP status " + status + (!ret?"(FAIL)":""));\r
- } catch (Exception e) {\r
- logException(e, "dowloading " + remoteFilePath);\r
- \r
- } finally {\r
- if (!ret && targetFile.exists()) {\r
- targetFile.delete();\r
- }\r
- get.releaseConnection(); // let the connection available for other methods\r
- }\r
- return ret;\r
- }\r
- \r
- /**\r
- * Deletes a remote file via webdav\r
- * @param remoteFilePath Remote file path of the file to delete, in URL DECODED format.\r
- * @return\r
- */\r
- public boolean deleteFile(String remoteFilePath) {\r
- boolean ret = false;\r
- DavMethod delete = new DeleteMethod(mUri.toString() + WebdavUtils.encodePath(remoteFilePath));\r
- try {\r
- int status = executeMethod(delete);\r
- ret = (status == HttpStatus.SC_OK || status == HttpStatus.SC_ACCEPTED || status == HttpStatus.SC_NO_CONTENT);\r
- exhaustResponse(delete.getResponseBodyAsStream());\r
- \r
- Log.e(TAG, "DELETE of " + remoteFilePath + " finished with HTTP status " + status + (!ret?"(FAIL)":""));\r
- \r
- } catch (Exception e) {\r
- logException(e, "deleting " + remoteFilePath);\r
- \r
- } finally {\r
- delete.releaseConnection(); // let the connection available for other methods\r
- }\r
- return ret;\r
- }\r
- \r
- \r
- public void setDataTransferProgressListener(OnDatatransferProgressListener listener) {\r
- mDataTransferListener = listener;\r
- }\r
- \r
- /**\r
- * Creates or update a file in the remote server with the contents of a local file.\r
- * \r
- * @param localFile Path to the local file to upload.\r
- * @param remoteTarget Remote path to the file to create or update, URL DECODED\r
- * @param contentType MIME type of the file.\r
- * @return Status HTTP code returned by the server.\r
- * @throws IOException When a transport error that could not be recovered occurred while uploading the file to the server.\r
- * @throws HttpException When a violation of the HTTP protocol occurred. \r
- */\r
- public int putFile(String localFile, String remoteTarget, String contentType) throws HttpException, IOException {\r
- int status = -1;\r
- PutMethod put = new PutMethod(mUri.toString() + WebdavUtils.encodePath(remoteTarget));\r
- \r
- try {\r
- File f = new File(localFile);\r
- FileRequestEntity entity = new FileRequestEntity(f, contentType);\r
- entity.addOnDatatransferProgressListener(mDataTransferListener);\r
- put.setRequestEntity(entity);\r
- status = executeMethod(put);\r
- \r
- exhaustResponse(put.getResponseBodyAsStream());\r
- \r
- } finally {\r
- put.releaseConnection(); // let the connection available for other methods\r
- }\r
- return status;\r
- }\r
- \r
- /**\r
- * Tries to log in to the current URI, with the current credentials\r
- * \r
- * @return A {@link HttpStatus}-Code of the result. SC_OK is good.\r
- */\r
- public int tryToLogin() {\r
- int status = 0;\r
- HeadMethod head = new HeadMethod(mUri.toString());\r
- try {\r
- status = executeMethod(head);\r
- boolean result = status == HttpStatus.SC_OK;\r
- Log.d(TAG, "HEAD for " + mUri + " finished with HTTP status " + status + (!result?"(FAIL)":""));\r
- exhaustResponse(head.getResponseBodyAsStream());\r
- \r
- } catch (Exception e) {\r
- logException(e, "trying to login at " + mUri.toString());\r
- \r
- } finally {\r
- head.releaseConnection();\r
- }\r
- return status;\r
- }\r
- \r
- \r
- /**\r
- * Check if a file exists in the OC server\r
- * \r
- * @return 'true' if the file exists; 'false' it doesn't exist\r
- * @throws Exception When the existence could not be determined\r
- */\r
- public boolean existsFile(String path) throws IOException, HttpException {\r
- HeadMethod head = new HeadMethod(mUri.toString() + WebdavUtils.encodePath(path));\r
- try {\r
- int status = executeMethod(head);\r
- Log.d(TAG, "HEAD to " + path + " finished with HTTP status " + status + ((status != HttpStatus.SC_OK)?"(FAIL)":""));\r
- exhaustResponse(head.getResponseBodyAsStream());\r
- return (status == HttpStatus.SC_OK);\r
- \r
- } finally {\r
- head.releaseConnection(); // let the connection available for other methods\r
- }\r
- }\r
- \r
- \r
- /**\r
- * Requests the received method with the received timeout (milliseconds).\r
- * \r
- * Executes the method through the inherited HttpClient.executedMethod(method).\r
- * \r
- * Sets the socket and connection timeouts only for the method received.\r
- * \r
- * The timeouts are both in milliseconds; 0 means 'infinite'; < 0 means 'do not change the default'\r
- * \r
- * @param method HTTP method request.\r
- * @param readTimeout Timeout to set for data reception\r
- * @param conntionTimout Timeout to set for connection establishment\r
- */\r
- public int executeMethod(HttpMethodBase method, int readTimeout, int connectionTimeout) throws HttpException, IOException {\r
- int oldSoTimeout = getParams().getSoTimeout();\r
- int oldConnectionTimeout = getHttpConnectionManager().getParams().getConnectionTimeout();\r
- try {\r
- if (readTimeout >= 0) { \r
- method.getParams().setSoTimeout(readTimeout); // this should be enough...\r
- getParams().setSoTimeout(readTimeout); // ... but this looks like necessary for HTTPS\r
- }\r
- if (connectionTimeout >= 0) {\r
- getHttpConnectionManager().getParams().setConnectionTimeout(connectionTimeout);\r
- }\r
- return executeMethod(method);\r
- } finally {\r
- getParams().setSoTimeout(oldSoTimeout);\r
- getHttpConnectionManager().getParams().setConnectionTimeout(oldConnectionTimeout);\r
- }\r
- }\r
- \r
- /**\r
- * Exhausts a not interesting HTTP response. Encouraged by HttpClient documentation.\r
- * \r
- * @param responseBodyAsStream InputStream with the HTTP response to exhaust.\r
- */\r
- public void exhaustResponse(InputStream responseBodyAsStream) {\r
- if (responseBodyAsStream != null) {\r
- try {\r
- while (responseBodyAsStream.read(sExhaustBuffer) >= 0);\r
- responseBodyAsStream.close();\r
- \r
- } catch (IOException io) {\r
- Log.e(TAG, "Unexpected exception while exhausting not interesting HTTP response; will be IGNORED", io);\r
- }\r
- }\r
- }\r
- \r
- \r
- /**\r
- * Logs an exception triggered in a HTTP request. \r
- * \r
- * @param e Caught exception.\r
- * @param doing Suffix to add at the end of the logged message.\r
- */\r
- private void logException(Exception e, String doing) {\r
- if (e instanceof HttpException) {\r
- Log.e(TAG, "HTTP violation while " + doing, e);\r
- \r
- } else if (e instanceof IOException) {\r
- Log.e(TAG, "Unrecovered transport exception while " + doing, e);\r
- \r
- } else {\r
- Log.e(TAG, "Unexpected exception while " + doing, e);\r
- }\r
- }\r
- \r
- \r
- /**\r
- * Sets the connection and wait-for-data timeouts to be applied by default to the methods performed by this client.\r
- */\r
- public void setDefaultTimeouts(int defaultDataTimeout, int defaultConnectionTimeout) {\r
- getParams().setSoTimeout(defaultDataTimeout);\r
- getHttpConnectionManager().getParams().setConnectionTimeout(defaultConnectionTimeout);\r
- }\r
- \r
- /**\r
- * Sets the base URI for the helper methods that receive paths as parameters, instead of full URLs\r
- * @param uri\r
- */\r
- public void setBaseUri(Uri uri) {\r
- mUri = uri;\r
- }\r
- \r
- public Uri getBaseUri() {\r
- return mUri;\r
- }\r
- \r
- \r
+ /* 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 <http://www.gnu.org/licenses/>.
+ *
+ */
++
+ package eu.alefzero.webdav;
+
+ import java.io.BufferedInputStream;
+ import java.io.File;
+ import java.io.FileOutputStream;
+ import java.io.IOException;
+ import java.io.InputStream;
++import java.util.ArrayList;
++import java.util.List;
+
+ import org.apache.commons.httpclient.Credentials;
++import org.apache.commons.httpclient.HostConfiguration;
+ import org.apache.commons.httpclient.HttpClient;
+ import org.apache.commons.httpclient.HttpConnectionManager;
+ import org.apache.commons.httpclient.HttpException;
++import org.apache.commons.httpclient.HttpMethod;
+ import org.apache.commons.httpclient.HttpMethodBase;
++import org.apache.commons.httpclient.HttpState;
+ import org.apache.commons.httpclient.HttpVersion;
+ import org.apache.commons.httpclient.UsernamePasswordCredentials;
++import org.apache.commons.httpclient.auth.AuthPolicy;
+ import org.apache.commons.httpclient.auth.AuthScope;
+ import org.apache.commons.httpclient.methods.GetMethod;
+ import org.apache.commons.httpclient.methods.HeadMethod;
+ import org.apache.commons.httpclient.methods.PutMethod;
+ import org.apache.commons.httpclient.params.HttpMethodParams;
+ import org.apache.http.HttpStatus;
+ import org.apache.http.params.CoreProtocolPNames;
+ import org.apache.jackrabbit.webdav.client.methods.DavMethod;
+ import org.apache.jackrabbit.webdav.client.methods.DeleteMethod;
-import org.apache.jackrabbit.webdav.client.methods.MkColMethod;
+
+ import com.owncloud.android.Log_OC;
+
++import com.owncloud.android.network.BearerAuthScheme;
++import com.owncloud.android.network.BearerCredentials;
++
+ import android.net.Uri;
-import android.util.Log;
+
+ public class WebdavClient extends HttpClient {
+ private Uri mUri;
+ private Credentials mCredentials;
+ final private static String TAG = "WebdavClient";
+ private static final String USER_AGENT = "Android-ownCloud";
+
+ private OnDatatransferProgressListener mDataTransferListener;
+ static private byte[] sExhaustBuffer = new byte[1024];
+
+ /**
+ * Constructor
+ */
+ public WebdavClient(HttpConnectionManager connectionMgr) {
+ super(connectionMgr);
+ Log_OC.d(TAG, "Creating WebdavClient");
+ getParams().setParameter(HttpMethodParams.USER_AGENT, USER_AGENT);
+ getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
+ }
+
- public void setCredentials(String username, String password) {
- getParams().setAuthenticationPreemptive(true);
- getState().setCredentials(AuthScope.ANY,
- getCredentials(username, password));
++ public void setBearerCredentials(String accessToken) {
++ AuthPolicy.registerAuthScheme(BearerAuthScheme.AUTH_POLICY, BearerAuthScheme.class);
++
++ List<String> authPrefs = new ArrayList<String>(1);
++ authPrefs.add(BearerAuthScheme.AUTH_POLICY);
++ getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
++
++ mCredentials = new BearerCredentials(accessToken);
++ getState().setCredentials(AuthScope.ANY, mCredentials);
+ }
+
- private Credentials getCredentials(String username, String password) {
- if (mCredentials == null)
- mCredentials = new UsernamePasswordCredentials(username, password);
- return mCredentials;
++ public void setBasicCredentials(String username, String password) {
++ List<String> authPrefs = new ArrayList<String>(1);
++ authPrefs.add(AuthPolicy.BASIC);
++ getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
++
++ getParams().setAuthenticationPreemptive(true);
++ mCredentials = new UsernamePasswordCredentials(username, password);
++ getState().setCredentials(AuthScope.ANY, mCredentials);
+ }
+
+ /**
+ * Downloads a file in remoteFilepath to the local targetPath.
+ *
+ * @param remoteFilepath Path to the file in the remote server, URL DECODED.
+ * @param targetFile Local path to save the downloaded file.
+ * @return 'True' when the file is successfully downloaded.
+ */
+ public boolean downloadFile(String remoteFilePath, File targetFile) {
+ boolean ret = false;
+ GetMethod get = new GetMethod(mUri.toString() + WebdavUtils.encodePath(remoteFilePath));
+
+ try {
+ int status = executeMethod(get);
+ if (status == HttpStatus.SC_OK) {
+ targetFile.createNewFile();
+ BufferedInputStream bis = new BufferedInputStream(
+ get.getResponseBodyAsStream());
+ FileOutputStream fos = new FileOutputStream(targetFile);
+
+ byte[] bytes = new byte[4096];
+ int readResult;
+ while ((readResult = bis.read(bytes)) != -1) {
+ if (mDataTransferListener != null)
+ mDataTransferListener.onTransferProgress(readResult);
+ fos.write(bytes, 0, readResult);
+ }
+ fos.close();
+ ret = true;
+ } else {
+ exhaustResponse(get.getResponseBodyAsStream());
+ }
+ Log_OC.e(TAG, "Download of " + remoteFilePath + " to " + targetFile + " finished with HTTP status " + status + (!ret?"(FAIL)":""));
+
+ } catch (Exception e) {
+ logException(e, "dowloading " + remoteFilePath);
+
+ } finally {
+ if (!ret && targetFile.exists()) {
+ targetFile.delete();
+ }
+ get.releaseConnection(); // let the connection available for other methods
+ }
+ return ret;
+ }
+
++
+ /**
+ * Deletes a remote file via webdav
+ * @param remoteFilePath Remote file path of the file to delete, in URL DECODED format.
+ * @return
+ */
+ public boolean deleteFile(String remoteFilePath) {
+ boolean ret = false;
+ DavMethod delete = new DeleteMethod(mUri.toString() + WebdavUtils.encodePath(remoteFilePath));
+ try {
+ int status = executeMethod(delete);
+ ret = (status == HttpStatus.SC_OK || status == HttpStatus.SC_ACCEPTED || status == HttpStatus.SC_NO_CONTENT);
+ exhaustResponse(delete.getResponseBodyAsStream());
+
+ Log_OC.e(TAG, "DELETE of " + remoteFilePath + " finished with HTTP status " + status + (!ret?"(FAIL)":""));
+
+ } catch (Exception e) {
+ logException(e, "deleting " + remoteFilePath);
+
+ } finally {
+ delete.releaseConnection(); // let the connection available for other methods
+ }
+ return ret;
+ }
+
+
+ public void setDataTransferProgressListener(OnDatatransferProgressListener listener) {
+ mDataTransferListener = listener;
+ }
+
+ /**
+ * Creates or update a file in the remote server with the contents of a local file.
+ *
+ * @param localFile Path to the local file to upload.
+ * @param remoteTarget Remote path to the file to create or update, URL DECODED
+ * @param contentType MIME type of the file.
+ * @return Status HTTP code returned by the server.
+ * @throws IOException When a transport error that could not be recovered occurred while uploading the file to the server.
+ * @throws HttpException When a violation of the HTTP protocol occurred.
+ */
+ public int putFile(String localFile, String remoteTarget, String contentType) throws HttpException, IOException {
+ int status = -1;
+ PutMethod put = new PutMethod(mUri.toString() + WebdavUtils.encodePath(remoteTarget));
+
+ try {
+ File f = new File(localFile);
+ FileRequestEntity entity = new FileRequestEntity(f, contentType);
+ entity.addDatatransferProgressListener(mDataTransferListener);
+ put.setRequestEntity(entity);
+ status = executeMethod(put);
+
+ exhaustResponse(put.getResponseBodyAsStream());
+
+ } finally {
+ put.releaseConnection(); // let the connection available for other methods
+ }
+ return status;
+ }
+
+ /**
+ * Tries to log in to the current URI, with the current credentials
+ *
+ * @return A {@link HttpStatus}-Code of the result. SC_OK is good.
+ */
+ public int tryToLogin() {
+ int status = 0;
+ HeadMethod head = new HeadMethod(mUri.toString());
+ try {
+ status = executeMethod(head);
+ boolean result = status == HttpStatus.SC_OK;
+ Log_OC.d(TAG, "HEAD for " + mUri + " finished with HTTP status " + status + (!result?"(FAIL)":""));
+ exhaustResponse(head.getResponseBodyAsStream());
+
+ } catch (Exception e) {
+ logException(e, "trying to login at " + mUri.toString());
+
+ } finally {
+ head.releaseConnection();
+ }
+ return status;
+ }
+
+ /**
- * Creates a remote directory with the received path.
- *
- * @param path Path of the directory to create, URL DECODED
- * @return 'True' when the directory is successfully created
- */
- public boolean createDirectory(String path) {
- boolean result = false;
- int status = -1;
- MkColMethod mkcol = new MkColMethod(mUri.toString() + WebdavUtils.encodePath(path));
- try {
- Log_OC.d(TAG, "Creating directory " + path);
- status = executeMethod(mkcol);
- Log_OC.d(TAG, "Status returned: " + status);
- result = mkcol.succeeded();
-
- Log_OC.d(TAG, "MKCOL to " + path + " finished with HTTP status " + status + (!result?"(FAIL)":""));
- exhaustResponse(mkcol.getResponseBodyAsStream());
-
- } catch (Exception e) {
- logException(e, "creating directory " + path);
-
- } finally {
- mkcol.releaseConnection(); // let the connection available for other methods
- }
- return result;
- }
-
-
- /**
+ * Check if a file exists in the OC server
+ *
+ * @return 'true' if the file exists; 'false' it doesn't exist
+ * @throws Exception When the existence could not be determined
+ */
+ public boolean existsFile(String path) throws IOException, HttpException {
+ HeadMethod head = new HeadMethod(mUri.toString() + WebdavUtils.encodePath(path));
+ try {
+ int status = executeMethod(head);
+ Log_OC.d(TAG, "HEAD to " + path + " finished with HTTP status " + status + ((status != HttpStatus.SC_OK)?"(FAIL)":""));
+ exhaustResponse(head.getResponseBodyAsStream());
+ return (status == HttpStatus.SC_OK);
+
+ } finally {
+ head.releaseConnection(); // let the connection available for other methods
+ }
+ }
-
++
+ /**
+ * Requests the received method with the received timeout (milliseconds).
+ *
+ * Executes the method through the inherited HttpClient.executedMethod(method).
+ *
+ * Sets the socket and connection timeouts only for the method received.
+ *
+ * The timeouts are both in milliseconds; 0 means 'infinite'; < 0 means 'do not change the default'
+ *
+ * @param method HTTP method request.
+ * @param readTimeout Timeout to set for data reception
+ * @param conntionTimout Timeout to set for connection establishment
+ */
+ public int executeMethod(HttpMethodBase method, int readTimeout, int connectionTimeout) throws HttpException, IOException {
+ int oldSoTimeout = getParams().getSoTimeout();
+ int oldConnectionTimeout = getHttpConnectionManager().getParams().getConnectionTimeout();
+ try {
+ if (readTimeout >= 0) {
+ method.getParams().setSoTimeout(readTimeout); // this should be enough...
+ getParams().setSoTimeout(readTimeout); // ... but this looks like necessary for HTTPS
+ }
+ if (connectionTimeout >= 0) {
+ getHttpConnectionManager().getParams().setConnectionTimeout(connectionTimeout);
+ }
+ return executeMethod(method);
+ } finally {
+ getParams().setSoTimeout(oldSoTimeout);
+ getHttpConnectionManager().getParams().setConnectionTimeout(oldConnectionTimeout);
+ }
+ }
+
+ /**
+ * Exhausts a not interesting HTTP response. Encouraged by HttpClient documentation.
+ *
+ * @param responseBodyAsStream InputStream with the HTTP response to exhaust.
+ */
+ public void exhaustResponse(InputStream responseBodyAsStream) {
+ if (responseBodyAsStream != null) {
+ try {
+ while (responseBodyAsStream.read(sExhaustBuffer) >= 0);
+ responseBodyAsStream.close();
+
+ } catch (IOException io) {
+ Log_OC.e(TAG, "Unexpected exception while exhausting not interesting HTTP response; will be IGNORED", io);
+ }
+ }
+ }
+
+ /**
+ * Logs an exception triggered in a HTTP request.
+ *
+ * @param e Caught exception.
+ * @param doing Suffix to add at the end of the logged message.
+ */
+ private void logException(Exception e, String doing) {
+ if (e instanceof HttpException) {
+ Log_OC.e(TAG, "HTTP violation while " + doing, e);
+
+ } else if (e instanceof IOException) {
+ Log_OC.e(TAG, "Unrecovered transport exception while " + doing, e);
+
+ } else {
+ Log_OC.e(TAG, "Unexpected exception while " + doing, e);
+ }
+ }
+
+
+ /**
+ * Sets the connection and wait-for-data timeouts to be applied by default to the methods performed by this client.
+ */
+ public void setDefaultTimeouts(int defaultDataTimeout, int defaultConnectionTimeout) {
+ getParams().setSoTimeout(defaultDataTimeout);
+ getHttpConnectionManager().getParams().setConnectionTimeout(defaultConnectionTimeout);
+ }
+
+ /**
+ * Sets the base URI for the helper methods that receive paths as parameters, instead of full URLs
+ * @param uri
+ */
+ public void setBaseUri(Uri uri) {
+ mUri = uri;
+ }
+
+ public Uri getBaseUri() {
+ return mUri;
+ }
+
+ @Override\r
+ public int executeMethod(HostConfiguration hostconfig, final HttpMethod method, final HttpState state) throws IOException, HttpException {\r
+ if (mCredentials instanceof BearerCredentials) {\r
+ method.getHostAuthState().setAuthScheme(AuthPolicy.getAuthScheme(BearerAuthScheme.AUTH_POLICY));\r
+ method.getHostAuthState().setAuthAttempted(true);\r
+ }\r
+ return super.executeMethod(hostconfig, method, state);\r
+ }\r
+\r
+ \r
+ public final Credentials getCredentials() {\r
+ return mCredentials;\r
+ }\r
+\r
- }\r
+ }
import org.apache.jackrabbit.webdav.property.DavPropertyName;
import org.apache.jackrabbit.webdav.property.DavPropertySet;
+ import com.owncloud.android.Log_OC;
+
import android.net.Uri;
--import android.util.Log;
public class WebdavEntry {
private String mName, mPath, mUri, mContentType;