From: David A. Velasco Date: Thu, 25 Apr 2013 17:39:22 +0000 (+0200) Subject: Merge branch 'develop' into oauth_login X-Git-Tag: oc-android-1.4.3~29^2~4 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/2946d8dd69cf8d30a3fc2447ac931675989e8eff?hp=--cc Merge branch 'develop' into oauth_login Conflicts: AndroidManifest.xml res/values/strings.xml src/com/owncloud/android/Uploader.java src/com/owncloud/android/authentication/AccountAuthenticator.java src/com/owncloud/android/authenticator/AuthenticationRunnable.java src/com/owncloud/android/authenticator/OnAuthenticationResultListener.java src/com/owncloud/android/authenticator/OnConnectCheckListener.java src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java src/com/owncloud/android/files/services/FileDownloader.java src/com/owncloud/android/files/services/FileOperation.java src/com/owncloud/android/files/services/FileUploader.java src/com/owncloud/android/network/OwnCloudClientUtils.java src/com/owncloud/android/operations/RemoteOperationResult.java src/com/owncloud/android/operations/UpdateOCVersionOperation.java src/com/owncloud/android/syncadapter/FileSyncAdapter.java src/com/owncloud/android/ui/activity/AccountSelectActivity.java src/com/owncloud/android/ui/activity/AuthenticatorActivity.java src/com/owncloud/android/ui/activity/FileDisplayActivity.java src/com/owncloud/android/ui/fragment/FileDetailFragment.java src/eu/alefzero/webdav/WebdavClient.java --- 2946d8dd69cf8d30a3fc2447ac931675989e8eff diff --cc AndroidManifest.xml index 745d09f3,421880f6..ba32f6cf --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@@ -1,159 -1,163 +1,170 @@@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - ++ + + + + + + + + + + + + + + + + + + + + + + + ++ android:theme="@style/Theme.ownCloud.noActionBar" ++ android:launchMode="singleTask"> ++ ++ ++ ++ ++ ++ + + + + + + + + + + - + - - + + - - + + + + - - - - - - + + + + diff --cc res/values/strings.xml index 512f6569,ccbea5f1..ce9f543f --- a/res/values/strings.xml +++ b/res/values/strings.xml @@@ -106,19 -113,18 +113,19 @@@ %1$s was successfully downloaded Download failed Download of %1$s could not be completed + Not downloaded yet Choose account Contacts - Synchronization failed + Synchronization failed Synchronization of %1$s could not be completed - Conflicts found - %1$d kept-in-sync files could not be sync\'ed + Invalid credentials for %1$s + Conflicts found + %1$d kept-in-sync files could not be sync\'ed Kept-in-sync files failed - Contents of %1$d files could not be sync\'ed (%2$d conflicts) - Some local files were forgotten - %1$d files out of the %2$s directory could not be copied into - "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. - + Contents of %1$d files could not be sync\'ed (%2$d conflicts) + Some local files were forgotten + %1$d files out of the %2$s directory could not be copied into + "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. "Move all" "All files were moved" "Some files could not be moved" @@@ -170,21 -194,15 +195,21 @@@ Application couldn\'t find a server instance at the given path. Please check your path and try again. The server took too long to respond Malformed URL - SSL initialization failed - Unverified SSL server\'s identity - Unrecognized server version - Couldn\'t establish connection - Secure connection established + SSL initialization failed + Unverified SSL server\'s identity + Unrecognized server version + Couldn\'t establish connection + Secure connection established Login details - Invalid login / password + Invalid credentials + Unsuccessful authorization + Access denied by authorization server Wrong path given Internal server error, code %1$d + Unexpected state; please, enter the server URL again + Your authorization expired.\nPlease, authorize again + Your saved credentials are invalid.\nPlease, enter the current credentials + Application terminated unexpectedly. Would you like to submit a crash report? Send report Don\'t send report @@@ -208,24 -223,12 +230,19 @@@ Enter a new name "Local copy could not be renamed; try a different name" "Rename could not be completed" - - Remote file could not be checked - File contents already synchronized - + Remote file could not be checked + File contents already synchronized Directory could not be created - Wait a moment - "Unexpected problem ; please select the file from a different app" No file was selected + + oAuth2 URL + Login with oAuth2. + Connecting to oAuth2 server… + Please, open a web browser and go to:\n%1$s.\nValidate this code there:\n%2$s + Connection to this URL not available. + Warning The identity of the site could not be verified - The server certificate is not trusted diff --cc src/com/owncloud/android/DisplayUtils.java index 70a96727,70d53283..d2939c87 --- a/src/com/owncloud/android/DisplayUtils.java +++ b/src/com/owncloud/android/DisplayUtils.java @@@ -25,8 -24,8 +24,6 @@@ import java.util.HashMap import java.util.HashSet; import java.util.Set; --import android.util.Log; -- /** * A helper class for some string operations. * diff --cc src/com/owncloud/android/Uploader.java index 862ede81,7c402c9f..2ee5b4b9 --- a/src/com/owncloud/android/Uploader.java +++ b/src/com/owncloud/android/Uploader.java @@@ -1,406 -1,406 +1,405 @@@ - /* 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 as published by - * the Free Software Foundation, either version 2 of the License, or - * (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 details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - package com.owncloud.android; - - import java.io.File; - import java.util.ArrayList; - import java.util.HashMap; - import java.util.LinkedList; - import java.util.List; - import java.util.Stack; - import java.util.Vector; - - import com.owncloud.android.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 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; - - /** - * This can be used to upload things to an ownCloud instance. - * - * @author Bartek Przybylski - * - */ - public class Uploader extends ListActivity implements OnItemClickListener, android.view.View.OnClickListener { - private static final String TAG = "ownCloudUploader"; - - private Account mAccount; - private AccountManager mAccountManager; - private Stack mParents; - private ArrayList mStreamsToUpload; - private boolean mCreateDir; - private String mUploadPath; - private 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(); - 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.i(TAG, "No ownCloud account is available"); - showDialog(DIALOG_NO_ACCOUNT); - } else if (accounts.length > 1) { - Log.i(TAG, "More then one ownCloud is available"); - showDialog(DIALOG_MULTIPLE_ACCOUNT); - } else { - mAccount = accounts[0]; - mStorageManager = new FileDataStorageManager(mAccount, getContentResolver()); - populateDirectoryList(); - } - } else { - showDialog(DIALOG_NO_STREAM); - } - } - - @Override - protected Dialog onCreateDialog(final int id) { - final AlertDialog.Builder builder = new Builder(this); - switch (id) { - case DIALOG_WAITING: - ProgressDialog pDialog = new ProgressDialog(this); - pDialog.setIndeterminate(false); - pDialog.setCancelable(false); - pDialog.setMessage(getResources().getString(R.string.uploader_info_uploading)); - return pDialog; - case DIALOG_NO_ACCOUNT: - builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setTitle(R.string.uploader_wrn_no_account_title); - builder.setMessage(String.format(getString(R.string.uploader_wrn_no_account_text), getString(R.string.app_name))); - builder.setCancelable(false); - builder.setPositiveButton(R.string.uploader_wrn_no_account_setup_btn_text, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ECLAIR_MR1) { - // using string value since in API7 this - // constatn is not defined - // in API7 < this constatant is defined in - // Settings.ADD_ACCOUNT_SETTINGS - // and Settings.EXTRA_AUTHORITIES - Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT); - intent.putExtra("authorities", new String[] { 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.d(TAG, "on item click"); - Vector tmpfiles = mStorageManager.getDirectoryContent(mFile); - if (tmpfiles.size() <= 0) return; - // filter on dirtype - Vector files = new Vector(); - for (OCFile f : tmpfiles) - if (f.isDirectory()) - files.add(f); - if (files.size() < position) { - throw new IndexOutOfBoundsException("Incorrect item selected"); - } - mParents.push(files.get(position).getFileName()); - populateDirectoryList(); - } - - @Override - public void onClick(View v) { - // click on button - switch (v.getId()) { - case R.id.uploader_choose_folder: - mUploadPath = ""; // first element in mParents is root dir, represented by ""; init mUploadPath with "/" results in a "//" prefix - for (String p : mParents) - mUploadPath += p + OCFile.PATH_SEPARATOR; - Log.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.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.d(TAG, "Populating view with content of : " + full_path); - - mFile = mStorageManager.getFileByPath(full_path); - if (mFile != null) { - Vector files = mStorageManager.getDirectoryContent(mFile); - List> data = new LinkedList>(); - for (OCFile f : files) { - HashMap h = new HashMap(); - if (f.isDirectory()) { - h.put("dirname", f.getFileName()); - data.add(h); - } - } - SimpleAdapter sa = new SimpleAdapter(this, - data, - R.layout.uploader_list_item_layout, - new String[] {"dirname"}, - new int[] {R.id.textView1}); - setListAdapter(sa); - 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(); - 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(); - } - } - - } + /* ownCloud Android client application + * Copyright (C) 2012 Bartek Przybylski + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ ++ + package com.owncloud.android; + + import java.io.File; + import java.util.ArrayList; + import java.util.HashMap; + import java.util.LinkedList; + import java.util.List; + import java.util.Stack; + import java.util.Vector; + -import com.owncloud.android.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 mParents; + private ArrayList 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(); + 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 tmpfiles = mStorageManager.getDirectoryContent(mFile); + if (tmpfiles.size() <= 0) return; + // filter on dirtype + Vector files = new Vector(); + for (OCFile f : tmpfiles) + if (f.isDirectory()) + files.add(f); + if (files.size() < position) { + throw new IndexOutOfBoundsException("Incorrect item selected"); + } + mParents.push(files.get(position).getFileName()); + populateDirectoryList(); + } + + @Override + public void onClick(View v) { + // click on button + switch (v.getId()) { + case R.id.uploader_choose_folder: + mUploadPath = ""; // first element in mParents is root dir, represented by ""; init mUploadPath with "/" results in a "//" prefix + for (String p : mParents) + mUploadPath += p + OCFile.PATH_SEPARATOR; + Log_OC.d(TAG, "Uploading file to dir " + mUploadPath); + + uploadFiles(); + + break; + /*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 files = mStorageManager.getDirectoryContent(mFile); + List> data = new LinkedList>(); + for (OCFile f : files) { + HashMap h = new HashMap(); + if (f.isDirectory()) { + h.put("dirname", f.getFileName()); + data.add(h); + } + } + SimpleAdapter sa = new SimpleAdapter(this, + data, + R.layout.uploader_list_item_layout, + new String[] {"dirname"}, + new int[] {R.id.textView1}); + setListAdapter(sa); + 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(); + 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(); + } + } + + } diff --cc src/com/owncloud/android/authentication/AccountAuthenticator.java index d6acbbb1,00000000..aa129938 mode 100644,000000..100644 --- a/src/com/owncloud/android/authentication/AccountAuthenticator.java +++ b/src/com/owncloud/android/authentication/AccountAuthenticator.java @@@ -1,318 -1,0 +1,315 @@@ - /* 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 as published by - * the Free Software Foundation, either version 2 of the License, or - * (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 details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - - package com.owncloud.android.authentication; - - - import android.accounts.*; - import android.content.Context; - import android.content.Intent; - import android.os.Bundle; - import android.util.Log; - - - /** - * 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.i(TAG, "Adding account with type " + accountType - + " and auth token " + authTokenType); - try { - validateAccountType(accountType); - } catch (AuthenticatorException e) { - Log.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; - } - ++/* ownCloud Android client application ++ * Copyright (C) 2012 Bartek Przybylski ++ * Copyright (C) 2012-2013 ownCloud Inc. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2, ++ * as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ */ ++ ++package com.owncloud.android.authentication; ++ ++import android.accounts.*; ++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; ++ } ++ + /** + * {@inheritDoc} + */ + @Override + public Bundle confirmCredentials(AccountAuthenticatorResponse response, + Account account, Bundle options) throws NetworkErrorException { + try { + validateAccountType(account.type); + } catch (AuthenticatorException e) { - Log.e(TAG, "Failed to validate account type " + account.type + ": " ++ Log_OC.e(TAG, "Failed to validate account type " + account.type + ": " + + e.getMessage()); + e.printStackTrace(); + return e.getFailureBundle(); + } + Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, + response); + intent.putExtra(KEY_ACCOUNT, account); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + + setIntentFlags(intent); + + Bundle resultBundle = new Bundle(); + resultBundle.putParcelable(AccountManager.KEY_INTENT, intent); + return resultBundle; + } + + @Override + public Bundle editProperties(AccountAuthenticatorResponse response, + String accountType) { + return null; + } + - /** - * {@inheritDoc} - */ - @Override - public Bundle getAuthToken(AccountAuthenticatorResponse response, - Account account, String authTokenType, Bundle options) - throws NetworkErrorException { - /// validate parameters - try { - validateAccountType(account.type); - validateAuthTokenType(authTokenType); - } catch (AuthenticatorException e) { - Log.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_MULTIPLE_TASK); - //intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); // incompatible with the authorization code grant in OAuth - 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(); - } - } - ++ /** ++ * {@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) + throws UnsupportedAuthTokenTypeException { + if (!authTokenType.equals(AUTH_TOKEN_TYPE) && + !authTokenType.equals(AUTH_TOKEN_TYPE_PASSWORD) && + !authTokenType.equals(AUTH_TOKEN_TYPE_ACCESS_TOKEN) && + !authTokenType.equals(AUTH_TOKEN_TYPE_REFRESH_TOKEN) ) { + throw new UnsupportedAuthTokenTypeException(); + } + } + + public static class AuthenticatorException extends Exception { + private static final long serialVersionUID = 1L; + private Bundle mFailureBundle; + + public AuthenticatorException(int code, String errorMsg) { + mFailureBundle = new Bundle(); + mFailureBundle.putInt(AccountManager.KEY_ERROR_CODE, code); + mFailureBundle + .putString(AccountManager.KEY_ERROR_MESSAGE, errorMsg); + } + + public Bundle getFailureBundle() { + return mFailureBundle; + } + } + + public static class UnsupportedAccountTypeException extends + AuthenticatorException { + private static final long serialVersionUID = 1L; + + public UnsupportedAccountTypeException() { + super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, + "Unsupported account type"); + } + } + + public static class UnsupportedAuthTokenTypeException extends + AuthenticatorException { + private static final long serialVersionUID = 1L; + + public UnsupportedAuthTokenTypeException() { + super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, + "Unsupported auth token type"); + } + } + + public static class UnsupportedFeaturesException extends + AuthenticatorException { + public static final long serialVersionUID = 1L; + + public UnsupportedFeaturesException() { + super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, + "Unsupported features"); + } + } + + public static class AccessDeniedException extends AuthenticatorException { + public AccessDeniedException(int code, String errorMsg) { + super(AccountManager.ERROR_CODE_INVALID_RESPONSE, "Access Denied"); + } + + private static final long serialVersionUID = 1L; + + } +} diff --cc src/com/owncloud/android/authentication/AccountAuthenticatorService.java index 971d6f01,00000000..c6a77d55 mode 100644,000000..100644 --- a/src/com/owncloud/android/authentication/AccountAuthenticatorService.java +++ b/src/com/owncloud/android/authentication/AccountAuthenticatorService.java @@@ -1,42 -1,0 +1,41 @@@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. ++ * it under the terms of the GNU General Public License version 2, ++ * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.owncloud.android.authentication; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +public class AccountAuthenticatorService extends Service { + + private AccountAuthenticator mAuthenticator; + static final public String ACCOUNT_TYPE = "owncloud"; + + @Override + public void onCreate() { + super.onCreate(); + mAuthenticator = new AccountAuthenticator(this); + } + + @Override + public IBinder onBind(Intent intent) { + return mAuthenticator.getIBinder(); + } + +} diff --cc src/com/owncloud/android/authentication/AuthenticatorActivity.java index cc1b6482,00000000..ef5c21b5 mode 100644,000000..100644 --- a/src/com/owncloud/android/authentication/AuthenticatorActivity.java +++ b/src/com/owncloud/android/authentication/AuthenticatorActivity.java @@@ -1,1086 -1,0 +1,1085 @@@ +/* 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 as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. ++ * it under the terms of the GNU General Public License version 2, ++ * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.owncloud.android.authentication; + +import com.owncloud.android.AccountUtils; ++import com.owncloud.android.Log_OC; +import com.owncloud.android.ui.dialog.SslValidatorDialog; +import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener; +import com.owncloud.android.utils.OwnCloudVersion; +import com.owncloud.android.network.OwnCloudClientUtils; +import com.owncloud.android.operations.OwnCloudServerCheckOperation; +import com.owncloud.android.operations.ExistenceCheckOperation; +import com.owncloud.android.operations.OAuth2GetAccessToken; +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 android.accounts.Account; +import android.accounts.AccountAuthenticatorActivity; +import android.accounts.AccountManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.ContentResolver; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.text.InputType; - import android.util.Log; +import android.view.View; +import android.view.View.OnFocusChangeListener; +import android.view.Window; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.owncloud.android.R; + +import eu.alefzero.webdav.WebdavClient; + +/** + * This Activity is used to add an ownCloud account to the App + * + * @author Bartek Przybylski + * @author David A. Velasco + */ +public class AuthenticatorActivity extends AccountAuthenticatorActivity + implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeListener { + + private static final String TAG = AuthenticatorActivity.class.getSimpleName(); + + public static final String EXTRA_ACCOUNT = "ACCOUNT"; + public static final String EXTRA_USER_NAME = "USER_NAME"; + public static final String EXTRA_HOST_NAME = "HOST_NAME"; + public static final String EXTRA_ACTION = "ACTION"; + + private static final String KEY_HOST_URL_TEXT = "HOST_URL_TEXT"; + private static final String KEY_OC_VERSION = "OC_VERSION"; + private static final String KEY_ACCOUNT = "ACCOUNT"; + private static final String KEY_STATUS_TEXT = "STATUS_TEXT"; + private static final String KEY_STATUS_ICON = "STATUS_ICON"; + private static final String KEY_STATUS_CORRECT = "STATUS_CORRECT"; + private static final String KEY_IS_SSL_CONN = "IS_SSL_CONN"; + private static final String KEY_OAUTH2_STATUS_TEXT = "OAUTH2_STATUS_TEXT"; + private static final String KEY_OAUTH2_STATUS_ICON = "OAUTH2_STATUS_ICON"; + + private static final String OAUTH_MODE_ON = "on"; + private static final String OAUTH_MODE_OFF = "off"; + private static final String OAUTH_MODE_OPTIONAL = "optional"; + + private static final int DIALOG_LOGIN_PROGRESS = 0; + private static final int DIALOG_SSL_VALIDATOR = 1; + private static final int DIALOG_CERT_NOT_SAVED = 2; + private static final int DIALOG_OAUTH2_LOGIN_PROGRESS = 3; + + public static final byte ACTION_CREATE = 0; + public static final byte ACTION_UPDATE_TOKEN = 1; + + + private String mHostBaseUrl; + private OwnCloudVersion mDiscoveredVersion; + + private int mStatusText, mStatusIcon; + private boolean mStatusCorrect, mIsSslConn; + private int mOAuth2StatusText, mOAuth2StatusIcon; + + private final Handler mHandler = new Handler(); + private Thread mOperationThread; + private OwnCloudServerCheckOperation mOcServerChkOperation; + private ExistenceCheckOperation mAuthCheckOperation; + private RemoteOperationResult mLastSslUntrustedServerResult; + + private Uri mNewCapturedUriFromOAuth2Redirection; + + private AccountManager mAccountMgr; + private boolean mJustCreated; + private byte mAction; + private Account mAccount; + + private ImageView mRefreshButton; + private ImageView mViewPasswordButton; + private EditText mHostUrlInput; + private EditText mUsernameInput; + private EditText mPasswordInput; + private CheckBox mOAuth2Check; + private String mOAuthAccessToken; + private View mOkButton; + private TextView mAuthStatusLayout; + + private TextView mOAuthAuthEndpointText; + private TextView mOAuthTokenEndpointText; + + + /** + * {@inheritDoc} + * + * IMPORTANT ENTRY POINT 1: activity is shown to the user + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().requestFeature(Window.FEATURE_NO_TITLE); + + /// set view and get references to view elements + setContentView(R.layout.account_setup); + mRefreshButton = (ImageView) findViewById(R.id.refreshButton); + mViewPasswordButton = (ImageView) findViewById(R.id.viewPasswordButton); + mHostUrlInput = (EditText) findViewById(R.id.hostUrlInput); + mUsernameInput = (EditText) findViewById(R.id.account_username); + mPasswordInput = (EditText) findViewById(R.id.account_password); + mOAuthAuthEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_1); + mOAuthTokenEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_2); + mOAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check); + mOkButton = findViewById(R.id.buttonOK); + mAuthStatusLayout = (TextView) findViewById(R.id.auth_status_text); + + /// complete label for 'register account' button + Button b = (Button) findViewById(R.id.account_register); + if (b != null) { + b.setText(String.format(getString(R.string.auth_register), getString(R.string.app_name))); + } + + /// bind view elements to listeners + mHostUrlInput.setOnFocusChangeListener(this); + mPasswordInput.setOnFocusChangeListener(this); + + /// initialization + mAccountMgr = AccountManager.get(this); + mNewCapturedUriFromOAuth2Redirection = null; + mAction = getIntent().getByteExtra(EXTRA_ACTION, ACTION_CREATE); + mAccount = null; + + if (savedInstanceState == null) { + /// connection state and info + mStatusText = mStatusIcon = 0; + mStatusCorrect = false; + mIsSslConn = false; + + /// retrieve extras from intent + String tokenType = getIntent().getExtras().getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE); + boolean oAuthRequired = AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN.equals(tokenType) || OAUTH_MODE_ON.equals(getString(R.string.oauth2_mode)); + + mAccount = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT); + if (mAccount != null) { + String ocVersion = mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION); + if (ocVersion != null) { + mDiscoveredVersion = new OwnCloudVersion(ocVersion); + } + mHostBaseUrl = mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL); + mHostUrlInput.setText(mHostBaseUrl); + String userName = mAccount.name.substring(0, mAccount.name.lastIndexOf('@')); + mUsernameInput.setText(userName); + oAuthRequired = (mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null); + } + mOAuth2Check.setChecked(oAuthRequired); + changeViewByOAuth2Check(oAuthRequired); + + + } else { + loadSavedInstanceState(savedInstanceState); + } + + if (!OAUTH_MODE_OPTIONAL.equals(getString(R.string.oauth2_mode))) { + mOAuth2Check.setVisibility(View.GONE); + } + + if (mAction == ACTION_UPDATE_TOKEN) { + /// lock things that should not change + mHostUrlInput.setEnabled(false); + mUsernameInput.setEnabled(false); + mOAuth2Check.setVisibility(View.GONE); + checkOcServer(); + } + + mPasswordInput.setText(""); // clean password to avoid social hacking (disadvantage: password in removed if the device is turned aside) + mJustCreated = true; + } + + + /** + * Saves relevant state before {@link #onPause()} + * + * Do NOT save {@link #mNewCapturedUriFromOAuth2Redirection}; it keeps a temporal flag, intended to defer the + * processing of the redirection caught in {@link #onNewIntent(Intent)} until {@link #onResume()} + * + * See {@link #loadSavedInstanceState(Bundle)} + */ + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + /// connection state and info + outState.putInt(KEY_STATUS_TEXT, mStatusText); + outState.putInt(KEY_STATUS_ICON, mStatusIcon); + outState.putBoolean(KEY_STATUS_CORRECT, mStatusCorrect); + outState.putBoolean(KEY_IS_SSL_CONN, mIsSslConn); + + /// server data + if (mDiscoveredVersion != null) + outState.putString(KEY_OC_VERSION, mDiscoveredVersion.toString()); + outState.putString(KEY_HOST_URL_TEXT, mHostBaseUrl); + + /// account data, if updating + if (mAccount != null) + outState.putParcelable(KEY_ACCOUNT, mAccount); + + // Saving the state of oAuth2 components. + outState.putInt(KEY_OAUTH2_STATUS_ICON, mOAuth2StatusIcon); + outState.putInt(KEY_OAUTH2_STATUS_TEXT, mOAuth2StatusText); + + } + + + /** + * Loads saved state + * + * See {@link #onSaveInstanceState(Bundle)}. + * + * @param savedInstanceState Saved state, as received in {@link #onCreate(Bundle)}. + */ + private void loadSavedInstanceState(Bundle savedInstanceState) { + /// connection state and info + mStatusCorrect = savedInstanceState.getBoolean(KEY_STATUS_CORRECT); + mIsSslConn = savedInstanceState.getBoolean(KEY_IS_SSL_CONN); + mStatusText = savedInstanceState.getInt(KEY_STATUS_TEXT); + mStatusIcon = savedInstanceState.getInt(KEY_STATUS_ICON); + updateConnStatus(); + + /// UI settings depending upon connection + mOkButton.setEnabled(mStatusCorrect); // TODO really necessary? + if (!mStatusCorrect) + mRefreshButton.setVisibility(View.VISIBLE); // seems that setting visibility is necessary + else + mRefreshButton.setVisibility(View.INVISIBLE); + + /// server data + String ocVersion = savedInstanceState.getString(KEY_OC_VERSION); + if (ocVersion != null) + mDiscoveredVersion = new OwnCloudVersion(ocVersion); + mHostBaseUrl = savedInstanceState.getString(KEY_HOST_URL_TEXT); + + // account data, if updating + mAccount = savedInstanceState.getParcelable(KEY_ACCOUNT); + + // state of oAuth2 components + mOAuth2StatusIcon = savedInstanceState.getInt(KEY_OAUTH2_STATUS_ICON); + mOAuth2StatusText = savedInstanceState.getInt(KEY_OAUTH2_STATUS_TEXT); + // END of getting the state of oAuth2 components. + } + + + /** + * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION request + * is caught here. + * + * To make this possible, this activity needs to be qualified with android:launchMode = "singleTask" in the + * AndroidManifest.xml file. + */ + @Override + protected void onNewIntent (Intent intent) { - Log.d(TAG, "onNewIntent()"); ++ Log_OC.d(TAG, "onNewIntent()"); + Uri data = intent.getData(); + if (data != null && data.toString().startsWith(getString(R.string.oauth2_redirect_uri))) { + mNewCapturedUriFromOAuth2Redirection = data; + } + } + + + /** + * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION, and + * deferred in {@link #onNewIntent(Intent)}, is processed here. + */ + @Override + protected void onResume() { + super.onResume(); + // the state of mOAuth2Check is automatically recovered between configuration changes, but not before onCreate() finishes; so keep the next lines here + changeViewByOAuth2Check(mOAuth2Check.isChecked()); + if (mAction == ACTION_UPDATE_TOKEN && mJustCreated) { + if (mOAuth2Check.isChecked()) + Toast.makeText(this, R.string.auth_expired_oauth_token_toast, Toast.LENGTH_LONG).show(); + else + Toast.makeText(this, R.string.auth_expired_basic_auth_toast, Toast.LENGTH_LONG).show(); + } + + if (mNewCapturedUriFromOAuth2Redirection != null) { + getOAuth2AccessTokenFromCapturedRedirection(); + } + + mJustCreated = false; + } + + + /** + * Parses the redirection with the response to the GET AUTHORIZATION request to the + * oAuth server and requests for the access token (GET ACCESS TOKEN) + */ + private void getOAuth2AccessTokenFromCapturedRedirection() { + /// Parse data from OAuth redirection + String queryParameters = mNewCapturedUriFromOAuth2Redirection.getQuery(); + mNewCapturedUriFromOAuth2Redirection = null; + + /// Showing the dialog with instructions for the user. + showDialog(DIALOG_OAUTH2_LOGIN_PROGRESS); + + /// GET ACCESS TOKEN to the oAuth server + RemoteOperation operation = new OAuth2GetAccessToken( getString(R.string.oauth2_client_id), + getString(R.string.oauth2_redirect_uri), // TODO check - necessary here? + getString(R.string.oauth2_grant_type), + queryParameters); + //WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(getString(R.string.oauth2_url_endpoint_access)), getApplicationContext()); + WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mOAuthTokenEndpointText.getText().toString().trim()), getApplicationContext()); + operation.execute(client, this, mHandler); + } + + + + /** + * Handles the change of focus on the text inputs for the server URL and the password + */ + public void onFocusChange(View view, boolean hasFocus) { + if (view.getId() == R.id.hostUrlInput) { + onUrlInputFocusChanged((TextView) view, hasFocus); + + } else if (view.getId() == R.id.account_password) { + onPasswordFocusChanged((TextView) view, hasFocus); + } + } + + + /** + * Handles changes in focus on the text input for the server URL. + * + * IMPORTANT ENTRY POINT 2: When (!hasFocus), user wrote the server URL and changed to + * other field. The operation to check the existence of the server in the entered URL is + * started. + * + * When hasFocus: user 'comes back' to write again the server URL. + * + * @param hostInput TextView with the URL input field receiving the change of focus. + * @param hasFocus 'True' if focus is received, 'false' if is lost + */ + private void onUrlInputFocusChanged(TextView hostInput, boolean hasFocus) { + if (!hasFocus) { + checkOcServer(); + + } else { + // avoids that the 'connect' button can be clicked if the test was previously passed + mOkButton.setEnabled(false); + } + } + + + private void checkOcServer() { + String uri = mHostUrlInput.getText().toString().trim(); + if (uri.length() != 0) { + mStatusText = R.string.auth_testing_connection; + mStatusIcon = R.drawable.progress_small; + updateConnStatus(); + /** TODO cancel previous connection check if the user tries to ammend a wrong URL + if(mConnChkOperation != null) { + mConnChkOperation.cancel(); + } */ + mOcServerChkOperation = new OwnCloudServerCheckOperation(uri, this); + WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(uri), this); + mHostBaseUrl = ""; + mDiscoveredVersion = null; + mOperationThread = mOcServerChkOperation.execute(client, this, mHandler); + } else { + mRefreshButton.setVisibility(View.INVISIBLE); + mStatusText = 0; + mStatusIcon = 0; + updateConnStatus(); + } + } + + + /** + * Handles changes in focus on the text input for the password (basic authorization). + * + * When (hasFocus), the button to toggle password visibility is shown. + * + * When (!hasFocus), the button is made invisible and the password is hidden. + * + * @param passwordInput TextView with the password input field receiving the change of focus. + * @param hasFocus 'True' if focus is received, 'false' if is lost + */ + private void onPasswordFocusChanged(TextView passwordInput, boolean hasFocus) { + if (hasFocus) { + mViewPasswordButton.setVisibility(View.VISIBLE); + } else { + int input_type = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD; + passwordInput.setInputType(input_type); + mViewPasswordButton.setVisibility(View.INVISIBLE); + } + } + + + + /** + * Cancels the authenticator activity + * + * IMPORTANT ENTRY POINT 3: Never underestimate the importance of cancellation + * + * This method is bound in the layout/acceoun_setup.xml resource file. + * + * @param view Cancel button + */ + public void onCancelClick(View view) { + setResult(RESULT_CANCELED); // TODO review how is this related to AccountAuthenticator (debugging) + finish(); + } + + + + /** + * Checks the credentials of the user in the root of the ownCloud server + * before creating a new local account. + * + * For basic authorization, a check of existence of the root folder is + * performed. + * + * For OAuth, starts the flow to get an access token; the credentials test + * is postponed until it is available. + * + * IMPORTANT ENTRY POINT 4 + * + * @param view OK button + */ + public void onOkClick(View view) { + // this check should be unnecessary + if (mDiscoveredVersion == null || !mDiscoveredVersion.isVersionValid() || mHostBaseUrl == null || mHostBaseUrl.length() == 0) { + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_wtf_reenter_URL; + updateConnStatus(); + mOkButton.setEnabled(false); - Log.wtf(TAG, "The user was allowed to click 'connect' to an unchecked server!!"); ++ Log_OC.wtf(TAG, "The user was allowed to click 'connect' to an unchecked server!!"); + return; + } + + if (mOAuth2Check.isChecked()) { + startOauthorization(); + + } else { + checkBasicAuthorization(); + } + } + + + /** + * Tests the credentials entered by the user performing a check of existence on + * the root folder of the ownCloud server. + */ + private void checkBasicAuthorization() { + /// get the path to the root folder through WebDAV from the version server + String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, false); + + /// get basic credentials entered by user + String username = mUsernameInput.getText().toString(); + String password = mPasswordInput.getText().toString(); + + /// be gentle with the user + showDialog(DIALOG_LOGIN_PROGRESS); + + /// test credentials accessing the root folder + mAuthCheckOperation = new ExistenceCheckOperation("", this, false); + WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this); + client.setBasicCredentials(username, password); + mOperationThread = mAuthCheckOperation.execute(client, this, mHandler); + } + + + /** + * Starts the OAuth 'grant type' flow to get an access token, with + * a GET AUTHORIZATION request to the BUILT-IN authorization server. + */ + private void startOauthorization() { + // be gentle with the user + mStatusIcon = R.drawable.progress_small; + mStatusText = R.string.oauth_login_connection; + updateAuthStatus(); + + // GET AUTHORIZATION request + //Uri uri = Uri.parse(getString(R.string.oauth2_url_endpoint_auth)); + Uri uri = Uri.parse(mOAuthAuthEndpointText.getText().toString().trim()); + Uri.Builder uriBuilder = uri.buildUpon(); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_RESPONSE_TYPE, getString(R.string.oauth2_response_type)); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_REDIRECT_URI, getString(R.string.oauth2_redirect_uri)); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_CLIENT_ID, getString(R.string.oauth2_client_id)); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_SCOPE, getString(R.string.oauth2_scope)); + //uriBuilder.appendQueryParameter(OAuth2Constants.KEY_STATE, whateverwewant); + uri = uriBuilder.build(); - Log.d(TAG, "Starting browser to view " + uri.toString()); ++ Log_OC.d(TAG, "Starting browser to view " + uri.toString()); + Intent i = new Intent(Intent.ACTION_VIEW, uri); + startActivity(i); + } + + + /** + * Callback method invoked when a RemoteOperation executed by this Activity finishes. + * + * Dispatches the operation flow to the right method. + */ + @Override + public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { + + if (operation instanceof OwnCloudServerCheckOperation) { + onOcServerCheckFinish((OwnCloudServerCheckOperation) operation, result); + + } else if (operation instanceof OAuth2GetAccessToken) { + onGetOAuthAccessTokenFinish((OAuth2GetAccessToken)operation, result); + + } else if (operation instanceof ExistenceCheckOperation) { + onAuthorizationCheckFinish((ExistenceCheckOperation)operation, result); + + } + } + + + /** + * Processes the result of the server check performed when the user finishes the enter of the + * server URL. + * + * @param operation Server check performed. + * @param result Result of the check. + */ + private void onOcServerCheckFinish(OwnCloudServerCheckOperation operation, RemoteOperationResult result) { + /// update status icon and text + updateStatusIconAndText(result); + updateConnStatus(); + + /// save result state + mStatusCorrect = result.isSuccess(); + mIsSslConn = (result.getCode() == ResultCode.OK_SSL); + + /// very special case (TODO: move to a common place for all the remote operations) + if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) { + mLastSslUntrustedServerResult = result; + showDialog(DIALOG_SSL_VALIDATOR); + } + + /// update the visibility of the 'retry connection' button + if (!mStatusCorrect) + mRefreshButton.setVisibility(View.VISIBLE); + else + mRefreshButton.setVisibility(View.INVISIBLE); + + /// retrieve discovered version and normalize server URL + mDiscoveredVersion = operation.getDiscoveredVersion(); + mHostBaseUrl = mHostUrlInput.getText().toString().trim(); + if (!mHostBaseUrl.toLowerCase().startsWith("http://") && + !mHostBaseUrl.toLowerCase().startsWith("https://")) { + + if (mIsSslConn) { + mHostBaseUrl = "https://" + mHostBaseUrl; + } else { + mHostBaseUrl = "http://" + mHostBaseUrl; + } + + } + if (mHostBaseUrl.endsWith("/")) + mHostBaseUrl = mHostBaseUrl.substring(0, mHostBaseUrl.length() - 1); + + /// allow or not the user try to access the server + mOkButton.setEnabled(mStatusCorrect); + } + + + /** + * Chooses the right icon and text to show to the user for the received operation result. + * + * @param result Result of a remote operation performed in this activity + */ + private void updateStatusIconAndText(RemoteOperationResult result) { + mStatusText = mStatusIcon = 0; + + switch (result.getCode()) { + case OK_SSL: + mStatusIcon = android.R.drawable.ic_secure; + mStatusText = R.string.auth_secure_connection; + break; + + case OK_NO_SSL: + case OK: + if (mHostUrlInput.getText().toString().trim().toLowerCase().startsWith("http://") ) { + mStatusText = R.string.auth_connection_established; + mStatusIcon = R.drawable.ic_ok; + } else { + mStatusText = R.string.auth_nossl_plain_ok_title; + mStatusIcon = android.R.drawable.ic_partial_secure; + } + break; + + case SSL_RECOVERABLE_PEER_UNVERIFIED: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_ssl_unverified_server_title; + break; + + case BAD_OC_VERSION: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_bad_oc_version_title; + break; + case WRONG_CONNECTION: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_wrong_connection_title; + break; + case TIMEOUT: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_timeout_title; + break; + case INCORRECT_ADDRESS: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_incorrect_address_title; + break; + + case SSL_ERROR: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_ssl_general_error_title; + break; + + case UNAUTHORIZED: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_unauthorized; + break; + case HOST_NOT_AVAILABLE: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_unknown_host_title; + break; + case NO_NETWORK_CONNECTION: + mStatusIcon = R.drawable.no_network; + mStatusText = R.string.auth_no_net_conn_title; + break; + case INSTANCE_NOT_CONFIGURED: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_not_configured_title; + break; + case FILE_NOT_FOUND: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_incorrect_path_title; + break; + case OAUTH2_ERROR: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_oauth_error; + break; + case OAUTH2_ERROR_ACCESS_DENIED: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_oauth_error_access_denied; + break; + case UNHANDLED_HTTP_CODE: + case UNKNOWN_ERROR: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_unknown_error_title; + break; + + default: + break; + } + } + + + /** + * Processes the result of the request for and access token send + * to an OAuth authorization server. + * + * @param operation Operation performed requesting the access token. + * @param result Result of the operation. + */ + private void onGetOAuthAccessTokenFinish(OAuth2GetAccessToken operation, RemoteOperationResult result) { + try { + dismissDialog(DIALOG_OAUTH2_LOGIN_PROGRESS); + } catch (IllegalArgumentException e) { + // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens + } + + String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, true); + if (result.isSuccess() && webdav_path != null) { + /// be gentle with the user + showDialog(DIALOG_LOGIN_PROGRESS); + + /// time to test the retrieved access token on the ownCloud server + mOAuthAccessToken = ((OAuth2GetAccessToken)operation).getResultTokenMap().get(OAuth2Constants.KEY_ACCESS_TOKEN); - Log.d(TAG, "Got ACCESS TOKEN: " + mOAuthAccessToken); ++ Log_OC.d(TAG, "Got ACCESS TOKEN: " + mOAuthAccessToken); + mAuthCheckOperation = new ExistenceCheckOperation("", this, false); + WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this); + client.setBearerCredentials(mOAuthAccessToken); + mAuthCheckOperation.execute(client, this, mHandler); + + } else { + updateStatusIconAndText(result); + updateAuthStatus(); - Log.d(TAG, "Access failed: " + result.getLogMessage()); ++ Log_OC.d(TAG, "Access failed: " + result.getLogMessage()); + } + } + + + /** + * Processes the result of the access check performed to try the user credentials. + * + * Creates a new account through the AccountManager. + * + * @param operation Access check performed. + * @param result Result of the operation. + */ + private void onAuthorizationCheckFinish(ExistenceCheckOperation operation, RemoteOperationResult result) { + try { + dismissDialog(DIALOG_LOGIN_PROGRESS); + } catch (IllegalArgumentException e) { + // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens + } + + if (result.isSuccess()) { - Log.d(TAG, "Successful access - time to save the account"); ++ Log_OC.d(TAG, "Successful access - time to save the account"); + + if (mAction == ACTION_CREATE) { + createAccount(); + + } else { + updateToken(); + } + + finish(); + + } else { + updateStatusIconAndText(result); + updateAuthStatus(); - Log.d(TAG, "Access failed: " + result.getLogMessage()); ++ Log_OC.d(TAG, "Access failed: " + result.getLogMessage()); + } + } + + + /** + * Sets the proper response to get that the Account Authenticator that started this activity saves + * a new authorization token for mAccount. + */ + private void updateToken() { + Bundle response = new Bundle(); + response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name); + response.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type); + boolean isOAuth = mOAuth2Check.isChecked(); + if (isOAuth) { + response.putString(AccountManager.KEY_AUTHTOKEN, mOAuthAccessToken); + // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention + mAccountMgr.setAuthToken(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, mOAuthAccessToken); + } else { + response.putString(AccountManager.KEY_AUTHTOKEN, mPasswordInput.getText().toString()); + mAccountMgr.setPassword(mAccount, mPasswordInput.getText().toString()); + } + setAccountAuthenticatorResult(response); + } + + + /** + * Creates a new account through the Account Authenticator that started this activity. + * + * This makes the account permanent. + * + * TODO Decide how to name the OAuth accounts + */ + private void createAccount() { + /// create and save new ownCloud account + boolean isOAuth = mOAuth2Check.isChecked(); + + Uri uri = Uri.parse(mHostBaseUrl); + String username = mUsernameInput.getText().toString().trim(); + if (isOAuth) { + username = "OAuth_user" + (new java.util.Random(System.currentTimeMillis())).nextLong(); + } + String accountName = username + "@" + uri.getHost(); + if (uri.getPort() >= 0) { + accountName += ":" + uri.getPort(); + } + mAccount = new Account(accountName, AccountAuthenticator.ACCOUNT_TYPE); + if (isOAuth) { + mAccountMgr.addAccountExplicitly(mAccount, "", null); // with our implementation, the password is never input in the app + } else { + mAccountMgr.addAccountExplicitly(mAccount, mPasswordInput.getText().toString(), null); + } + + /// add the new account as default in preferences, if there is none already + Account defaultAccount = AccountUtils.getCurrentOwnCloudAccount(this); + if (defaultAccount == null) { + SharedPreferences.Editor editor = PreferenceManager + .getDefaultSharedPreferences(this).edit(); + editor.putString("select_oc_account", accountName); + editor.commit(); + } + + /// prepare result to return to the Authenticator + // TODO check again what the Authenticator makes with it; probably has the same effect as addAccountExplicitly, but it's not well done + final Intent intent = new Intent(); + intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, AccountAuthenticator.ACCOUNT_TYPE); + intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mAccount.name); + if (!isOAuth) + intent.putExtra(AccountManager.KEY_AUTHTOKEN, AccountAuthenticator.ACCOUNT_TYPE); // TODO check this; not sure it's right; maybe + intent.putExtra(AccountManager.KEY_USERDATA, username); + if (isOAuth) { + mAccountMgr.setAuthToken(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, mOAuthAccessToken); + } + /// add user data to the new account; TODO probably can be done in the last parameter addAccountExplicitly, or in KEY_USERDATA + mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION, mDiscoveredVersion.toString()); + mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL, mHostBaseUrl); + if (isOAuth) + mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2, "TRUE"); // TODO this flag should be unnecessary + + setAccountAuthenticatorResult(intent.getExtras()); + setResult(RESULT_OK, intent); + + /// immediately request for the synchronization of the new account + Bundle bundle = new Bundle(); + bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + ContentResolver.requestSync(mAccount, AccountAuthenticator.AUTHORITY, bundle); + } + + + /** + * {@inheritDoc} + * + * Necessary to update the contents of the SSL Dialog + * + * TODO move to some common place for all possible untrusted SSL failures + */ + @Override + protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { + switch (id) { + case DIALOG_LOGIN_PROGRESS: + case DIALOG_CERT_NOT_SAVED: + case DIALOG_OAUTH2_LOGIN_PROGRESS: + break; + case DIALOG_SSL_VALIDATOR: { + ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult); + break; + } + default: - Log.e(TAG, "Incorrect dialog called with id = " + id); ++ Log_OC.e(TAG, "Incorrect dialog called with id = " + id); + } + } + + + /** + * {@inheritDoc} + */ + @Override + protected Dialog onCreateDialog(int id) { + Dialog dialog = null; + switch (id) { + case DIALOG_LOGIN_PROGRESS: { + /// simple progress dialog + ProgressDialog working_dialog = new ProgressDialog(this); + working_dialog.setMessage(getResources().getString(R.string.auth_trying_to_login)); + working_dialog.setIndeterminate(true); + working_dialog.setCancelable(true); + working_dialog + .setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + /// TODO study if this is enough - Log.i(TAG, "Login canceled"); ++ Log_OC.i(TAG, "Login canceled"); + if (mOperationThread != null) { + mOperationThread.interrupt(); + finish(); + } + } + }); + dialog = working_dialog; + break; + } + case DIALOG_OAUTH2_LOGIN_PROGRESS: { + ProgressDialog working_dialog = new ProgressDialog(this); + working_dialog.setMessage(String.format("Getting authorization")); + working_dialog.setIndeterminate(true); + working_dialog.setCancelable(true); + working_dialog + .setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { - Log.i(TAG, "Login canceled"); ++ Log_OC.i(TAG, "Login canceled"); + finish(); + } + }); + dialog = working_dialog; + break; + } + case DIALOG_SSL_VALIDATOR: { + /// TODO start to use new dialog interface, at least for this (it is a FragmentDialog already) + dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this); + break; + } + case DIALOG_CERT_NOT_SAVED: { + AlertDialog.Builder 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: - Log.e(TAG, "Incorrect dialog called with id = " + id); ++ Log_OC.e(TAG, "Incorrect dialog called with id = " + id); + } + return dialog; + } + + + /** + * Starts and activity to open the 'new account' page in the ownCloud web site + * + * @param view 'Account register' button + */ + public void onRegisterClick(View view) { + Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_account_register))); + setResult(RESULT_CANCELED); + startActivity(register); + } + + + /** + * Updates the content and visibility state of the icon and text associated + * to the last check on the ownCloud server. + */ + private void updateConnStatus() { + ImageView iv = (ImageView) findViewById(R.id.action_indicator); + TextView tv = (TextView) findViewById(R.id.status_text); + + if (mStatusIcon == 0 && mStatusText == 0) { + iv.setVisibility(View.INVISIBLE); + tv.setVisibility(View.INVISIBLE); + } else { + iv.setImageResource(mStatusIcon); + tv.setText(mStatusText); + iv.setVisibility(View.VISIBLE); + tv.setVisibility(View.VISIBLE); + } + } + + + /** + * Updates the content and visibility state of the icon and text associated + * to the interactions with the OAuth authorization server. + */ + private void updateAuthStatus() { + if (mStatusIcon == 0 && mStatusText == 0) { + mAuthStatusLayout.setVisibility(View.INVISIBLE); + } else { + mAuthStatusLayout.setText(mStatusText); + mAuthStatusLayout.setCompoundDrawablesWithIntrinsicBounds(mStatusIcon, 0, 0, 0); + mAuthStatusLayout.setVisibility(View.VISIBLE); + } + } + + + /** + * Called when the refresh button in the input field for ownCloud host is clicked. + * + * Performs a new check on the URL in the input field. + * + * @param view Refresh 'button' + */ + public void onRefreshClick(View view) { + onFocusChange(mRefreshButton, false); + } + + + /** + * Called when the eye icon in the password field is clicked. + * + * Toggles the visibility of the password in the field. + * + * @param view 'View password' 'button' + */ + public void onViewPasswordClick(View view) { + int selectionStart = mPasswordInput.getSelectionStart(); + int selectionEnd = mPasswordInput.getSelectionEnd(); + int input_type = mPasswordInput.getInputType(); + if ((input_type & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) { + input_type = InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_VARIATION_PASSWORD; + } else { + input_type = InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; + } + mPasswordInput.setInputType(input_type); + mPasswordInput.setSelection(selectionStart, selectionEnd); + } + + + /** + * Called when the checkbox for OAuth authorization is clicked. + * + * Hides or shows the input fields for user & password. + * + * @param view 'View password' 'button' + */ + public void onCheckClick(View view) { + CheckBox oAuth2Check = (CheckBox)view; + changeViewByOAuth2Check(oAuth2Check.isChecked()); + + } + + /** + * Changes the visibility of input elements depending upon the kind of authorization + * chosen by the user: basic or OAuth + * + * @param checked 'True' when OAuth is selected. + */ + public void changeViewByOAuth2Check(Boolean checked) { + + if (checked) { + mOAuthAuthEndpointText.setVisibility(View.VISIBLE); + mOAuthTokenEndpointText.setVisibility(View.VISIBLE); + mUsernameInput.setVisibility(View.GONE); + mPasswordInput.setVisibility(View.GONE); + mViewPasswordButton.setVisibility(View.GONE); + } else { + mOAuthAuthEndpointText.setVisibility(View.GONE); + mOAuthTokenEndpointText.setVisibility(View.GONE); + mUsernameInput.setVisibility(View.VISIBLE); + mPasswordInput.setVisibility(View.VISIBLE); + mViewPasswordButton.setVisibility(View.INVISIBLE); + } + + } + + /** + * Called from SslValidatorDialog when a new server certificate was correctly saved. + */ + public void onSavedCertificate() { + mOperationThread = mOcServerChkOperation.retry(this, mHandler); + } + + /** + * Called from SslValidatorDialog when a new server certificate could not be saved + * when the user requested it. + */ + @Override + public void onFailedSavingCertificate() { + showDialog(DIALOG_CERT_NOT_SAVED); + } + +} diff --cc src/com/owncloud/android/authentication/OAuth2Constants.java index 227accbc,00000000..f96b6278 mode 100644,000000..100644 --- a/src/com/owncloud/android/authentication/OAuth2Constants.java +++ b/src/com/owncloud/android/authentication/OAuth2Constants.java @@@ -1,54 -1,0 +1,53 @@@ +/* ownCloud Android client application + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. ++ * it under the terms of the GNU General Public License version 2, ++ * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.owncloud.android.authentication; + +/** + * Constant values for OAuth 2 protocol. + * + * Includes required and optional parameter NAMES used in the 'authorization code' grant type. + * + * @author David A. Velasco + */ + +public class OAuth2Constants { + + /// Parameters to send to the Authorization Endpoint + public static final String KEY_RESPONSE_TYPE = "response_type"; + public static final String KEY_REDIRECT_URI = "redirect_uri"; + public static final String KEY_CLIENT_ID = "client_id"; + public static final String KEY_SCOPE = "scope"; + public static final String KEY_STATE = "state"; + + /// Additional parameters to send to the Token Endpoint + public static final String KEY_GRANT_TYPE = "grant_type"; + public static final String KEY_CODE = "code"; + + /// Parameters received in an OK response from the Token Endpoint + public static final String KEY_ACCESS_TOKEN = "access_token"; + public static final String KEY_TOKEN_TYPE = "token_type"; + public static final String KEY_EXPIRES_IN = "expires_in"; + public static final String KEY_REFRESH_TOKEN = "refresh_token"; + + /// Parameters in an ERROR response + public static final String KEY_ERROR = "error"; + public static final String KEY_ERROR_DESCRIPTION = "error_description"; + public static final String KEY_ERROR_URI = "error_uri"; + public static final String VALUE_ERROR_ACCESS_DENIED = "access_denied"; + +} diff --cc src/com/owncloud/android/datamodel/FileDataStorageManager.java index aa914c47,434bc472..1b0f083e --- a/src/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/com/owncloud/android/datamodel/FileDataStorageManager.java @@@ -40,7 -40,7 +40,6 @@@ import android.content.OperationApplica import android.database.Cursor; import android.net.Uri; import android.os.RemoteException; --import android.util.Log; public class FileDataStorageManager implements DataStorageManager { diff --cc src/com/owncloud/android/db/DbHandler.java index bd0fcd17,e3cfe4fb..889d1dbf --- a/src/com/owncloud/android/db/DbHandler.java +++ b/src/com/owncloud/android/db/DbHandler.java @@@ -1,94 -1,119 +1,118 @@@ - /* ownCloud Android client application - * Copyright (C) 2011-2012 Bartek Przybylski - * - * 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 2 of the License, or - * (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 details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - package com.owncloud.android.db; - - import android.content.ContentValues; - import android.content.Context; - import android.database.Cursor; - import android.database.sqlite.SQLiteDatabase; - import android.database.sqlite.SQLiteOpenHelper; - - /** - * Custom database helper for ownCloud - * - * @author Bartek Przybylski - * - */ - public class DbHandler { - private SQLiteDatabase mDB; - private OpenerHelper mHelper; - private final String mDatabaseName = "ownCloud"; - private final int mDatabaseVersion = 1; - - private final String TABLE_INSTANT_UPLOAD = "instant_upload"; - - public DbHandler(Context context) { - mHelper = new OpenerHelper(context); - mDB = mHelper.getWritableDatabase(); - } - - public void close() { - mDB.close(); - } - - public boolean putFileForLater(String filepath, String account) { - ContentValues cv = new ContentValues(); - cv.put("path", filepath); - cv.put("account", account); - return mDB.insert(TABLE_INSTANT_UPLOAD, null, cv) != -1; - } - - public Cursor getAwaitingFiles() { - return mDB.query(TABLE_INSTANT_UPLOAD, null, null, null, null, null, null); - } - - public void clearFiles() { - mDB.delete(TABLE_INSTANT_UPLOAD, null, null); - } - - /** - * - * @param localPath - * @param accountName - * @return true when one or more pendin files was removed - */ - public boolean removeIUPendingFile(String localPath, String accountName) { - return mDB.delete(TABLE_INSTANT_UPLOAD, - "path = ?", - new String[]{ localPath }) != 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);"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - } - } - } + /* ownCloud Android client application + * Copyright (C) 2011-2012 Bartek Przybylski + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + package com.owncloud.android.db; + + import com.owncloud.android.Log_OC; + + import 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;"); + + } + } + } diff --cc src/com/owncloud/android/extensions/ExtensionsAvailableDialog.java index ccef3408,ebe94501..15db896b --- a/src/com/owncloud/android/extensions/ExtensionsAvailableDialog.java +++ b/src/com/owncloud/android/extensions/ExtensionsAvailableDialog.java @@@ -23,7 -23,7 +23,6 @@@ import com.owncloud.android.R 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; diff --cc src/com/owncloud/android/extensions/ExtensionsListActivity.java index 5b167291,2a94418b..2f7290e8 --- a/src/com/owncloud/android/extensions/ExtensionsListActivity.java +++ b/src/com/owncloud/android/extensions/ExtensionsListActivity.java @@@ -35,7 -35,7 +35,6 @@@ import android.R 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 { diff --cc src/com/owncloud/android/files/BootupBroadcastReceiver.java index e322fa5d,97d04042..8a8c4306 --- a/src/com/owncloud/android/files/BootupBroadcastReceiver.java +++ b/src/com/owncloud/android/files/BootupBroadcastReceiver.java @@@ -24,7 -24,7 +24,6 @@@ import com.owncloud.android.files.servi import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; --import android.util.Log; public class BootupBroadcastReceiver extends BroadcastReceiver { diff --cc src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java index 781402d9,74d20f0d..c9272b6f --- a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java +++ b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java @@@ -36,22 -30,24 +35,20 @@@ import android.net.ConnectivityManager 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)) { diff --cc src/com/owncloud/android/files/OwnCloudFileObserver.java index 8f987fa4,10dbdfc3..2ea90c9a --- a/src/com/owncloud/android/files/OwnCloudFileObserver.java +++ b/src/com/owncloud/android/files/OwnCloudFileObserver.java @@@ -21,8 -20,10 +20,9 @@@ package com.owncloud.android.files import java.io.File; + import com.owncloud.android.Log_OC; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.network.OwnCloudClientUtils; import com.owncloud.android.operations.RemoteOperationResult; import com.owncloud.android.operations.SynchronizeFileOperation; import com.owncloud.android.operations.RemoteOperationResult.ResultCode; @@@ -33,7 -35,7 +33,6 @@@ import android.accounts.Account import android.content.Context; import android.content.Intent; import android.os.FileObserver; --import android.util.Log; public class OwnCloudFileObserver extends FileObserver { diff --cc src/com/owncloud/android/files/services/FileDownloader.java index 00d5f199,83686037..cf88ce04 --- a/src/com/owncloud/android/files/services/FileDownloader.java +++ b/src/com/owncloud/android/files/services/FileDownloader.java @@@ -1,447 -1,509 +1,535 @@@ - /* 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 as published by - * the Free Software Foundation, either version 2 of the License, or - * (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 details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - - package com.owncloud.android.files.services; - - import java.io.File; - import java.io.IOException; - import java.util.AbstractList; - import java.util.Iterator; - 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 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.util.Log; - import android.widget.RemoteViews; - - 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 mPendingDownloads = new ConcurrentHashMap(); - 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.e(TAG, "Not enough information provided in intent"); - return START_NOT_STICKY; - } - Account account = intent.getParcelableExtra(EXTRA_ACCOUNT); - OCFile file = intent.getParcelableExtra(EXTRA_FILE); - - AbstractList requestedDownloads = new Vector(); // dvelasco: now this always contains just one element, but that can change in a near future (download of multiple selection) - String downloadKey = buildRemoteName(account, file); - try { - DownloadFileOperation newDownload = new DownloadFileOperation(account, file); - mPendingDownloads.putIfAbsent(downloadKey, newDownload); - newDownload.addDatatransferProgressListener(this); - requestedDownloads.add(downloadKey); - sendBroadcastNewDownload(newDownload); - - } catch (IllegalArgumentException e) { - Log.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; - } - - - /** - * 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 { - - /** - * 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(); - } - } - - - /** - * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download. - * - * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download. - * - * @param account Owncloud account where the remote file is stored. - * @param file A file that could be in the queue of downloads. - */ - public boolean isDownloading(Account account, OCFile file) { - if (account == null || file == null) return false; - String targetKey = buildRemoteName(account, file); - synchronized (mPendingDownloads) { - if (file.isDirectory()) { - // this can be slow if there are many downloads :( - Iterator it = mPendingDownloads.keySet().iterator(); - boolean found = false; - while (it.hasNext() && !found) { - found = it.next().startsWith(targetKey); - } - return found; - } else { - return (mPendingDownloads.containsKey(targetKey)); - } - } - } - } - - - /** - * Download worker. Performs the pending downloads in the order they were requested. - * - * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}. - */ - private static class ServiceHandler extends Handler { - // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak - FileDownloader mService; - public ServiceHandler(Looper looper, FileDownloader service) { - super(looper); - if (service == null) - throw new IllegalArgumentException("Received invalid NULL in parameter 'service'"); - mService = service; - } - - @Override - public void handleMessage(Message msg) { - @SuppressWarnings("unchecked") - AbstractList requestedDownloads = (AbstractList) msg.obj; - if (msg.obj != null) { - Iterator it = requestedDownloads.iterator(); - while (it.hasNext()) { - mService.downloadFile(it.next()); - } - } - mService.stopSelf(msg.arg1); - } - } - - - - /** - * Core download method: requests a file to download and stores it. - * - * @param downloadKey Key to access the download to perform, contained in mPendingDownloads - */ - private void downloadFile(String downloadKey) { - - synchronized(mPendingDownloads) { - mCurrentDownload = mPendingDownloads.get(downloadKey); - } - - if (mCurrentDownload != null) { - - notifyDownloadStart(mCurrentDownload); - - RemoteOperationResult downloadResult = null; - try { - /// prepare client object to send the request to the ownCloud server - if (mDownloadClient == null || !mLastAccount.equals(mCurrentDownload.getAccount())) { - mLastAccount = mCurrentDownload.getAccount(); - mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver()); - mDownloadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext()); - } - - /// perform the download - if (downloadResult == null) { - downloadResult = mCurrentDownload.execute(mDownloadClient); - } - if (downloadResult.isSuccess()) { - saveDownloadedFile(); - } - - } catch (AccountsException e) { - Log.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e); - downloadResult = new RemoteOperationResult(e); - } catch (IOException e) { - Log.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 = 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; - 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 the contentIntent below - finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 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); - } - - } + /* ownCloud Android client application + * Copyright (C) 2012 Bartek Przybylski + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + package com.owncloud.android.files.services; + + import java.io.File; ++import java.io.IOException; + import java.util.AbstractList; + import java.util.HashMap; + import java.util.Iterator; + import java.util.Map; + import java.util.Vector; + import java.util.concurrent.ConcurrentHashMap; + import java.util.concurrent.ConcurrentMap; + ++import com.owncloud.android.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 mPendingDownloads = new ConcurrentHashMap(); + 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 requestedDownloads = new Vector(); // dvelasco: now this always contains just one element, but that can change in a near future (download of multiple selection) + String downloadKey = buildRemoteName(account, file); + try { + DownloadFileOperation newDownload = new DownloadFileOperation(account, file); + mPendingDownloads.putIfAbsent(downloadKey, newDownload); + newDownload.addDatatransferProgressListener(this); + newDownload.addDatatransferProgressListener((FileDownloaderBinder)mBinder); + requestedDownloads.add(downloadKey); + sendBroadcastNewDownload(newDownload); + + } catch (IllegalArgumentException e) { + Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage()); + return START_NOT_STICKY; + } + + if (requestedDownloads.size() > 0) { + Message msg = mServiceHandler.obtainMessage(); + msg.arg1 = startId; + msg.obj = requestedDownloads; + mServiceHandler.sendMessage(msg); + } + + return START_NOT_STICKY; + } + + + /** + * Provides a binder object that clients can use to perform operations on the queue of downloads, excepting the addition of new files. + * + * Implemented to perform cancellation, pause and resume of existing downloads. + */ + @Override + public IBinder onBind(Intent arg0) { + return mBinder; + } + + + /** + * Called when ALL the bound clients were onbound. + */ + @Override + public boolean onUnbind(Intent intent) { + ((FileDownloaderBinder)mBinder).clearListeners(); + return false; // not accepting rebinding (default behaviour) + } + + + /** + * Binder to let client components to perform operations on the queue of downloads. + * + * It provides by itself the available operations. + */ + public class FileDownloaderBinder extends Binder implements OnDatatransferProgressListener { + + /** + * Map of listeners that will be reported about progress of downloads from a {@link FileDownloaderBinder} instance + */ + private Map mBoundListeners = new HashMap(); + + + /** + * Cancels a pending or current download of a remote file. + * + * @param account Owncloud account where the remote file is stored. + * @param file A file in the queue of pending downloads + */ + public void cancel(Account account, OCFile file) { + DownloadFileOperation download = null; + synchronized (mPendingDownloads) { + download = mPendingDownloads.remove(buildRemoteName(account, file)); + } + if (download != null) { + download.cancel(); + } + } + + + public void clearListeners() { + mBoundListeners.clear(); + } + + + /** + * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download. + * + * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download. + * + * @param account Owncloud account where the remote file is stored. + * @param file A file that could be in the queue of downloads. + */ + public boolean isDownloading(Account account, OCFile file) { + if (account == null || file == null) return false; + String targetKey = buildRemoteName(account, file); + synchronized (mPendingDownloads) { + if (file.isDirectory()) { + // this can be slow if there are many downloads :( + Iterator it = mPendingDownloads.keySet().iterator(); + boolean found = false; + while (it.hasNext() && !found) { + found = it.next().startsWith(targetKey); + } + return found; + } else { + return (mPendingDownloads.containsKey(targetKey)); + } + } + } + + + /** + * Adds a listener interested in the progress of the download for a concrete file. + * + * @param listener Object to notify about progress of transfer. + * @param account ownCloud account holding the file of interest. + * @param file {@link OCfile} of interest for listener. + */ + public void addDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) { + if (account == null || file == null || listener == null) return; + String targetKey = buildRemoteName(account, file); + mBoundListeners.put(targetKey, listener); + } + + - + /** + * Removes a listener interested in the progress of the download for a concrete file. + * + * @param listener Object to notify about progress of transfer. + * @param account ownCloud account holding the file of interest. + * @param file {@link OCfile} of interest for listener. + */ + public void removeDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) { + if (account == null || file == null || listener == null) return; + String targetKey = buildRemoteName(account, file); + if (mBoundListeners.get(targetKey) == listener) { + mBoundListeners.remove(targetKey); + } + } + + + @Override + public void onTransferProgress(long progressRate) { + // old way, should not be in use any more + } + + + @Override + public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, + String fileName) { + String key = buildRemoteName(mCurrentDownload.getAccount(), mCurrentDownload.getFile()); + OnDatatransferProgressListener boundListener = mBoundListeners.get(key); + if (boundListener != null) { + boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName); + } + } + + } + + + /** + * Download worker. Performs the pending downloads in the order they were requested. + * + * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}. + */ + private static class ServiceHandler extends Handler { + // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak + FileDownloader mService; + public ServiceHandler(Looper looper, FileDownloader service) { + super(looper); + if (service == null) + throw new IllegalArgumentException("Received invalid NULL in parameter 'service'"); + mService = service; + } + + @Override + public void handleMessage(Message msg) { + @SuppressWarnings("unchecked") + AbstractList requestedDownloads = (AbstractList) msg.obj; + if (msg.obj != null) { + Iterator it = requestedDownloads.iterator(); + while (it.hasNext()) { + mService.downloadFile(it.next()); + } + } + mService.stopSelf(msg.arg1); + } + } + - + + /** + * Core download method: requests a file to download and stores it. + * + * @param downloadKey Key to access the download to perform, contained in mPendingDownloads + */ + private void downloadFile(String downloadKey) { + + synchronized(mPendingDownloads) { + mCurrentDownload = mPendingDownloads.get(downloadKey); + } + + if (mCurrentDownload != null) { + + notifyDownloadStart(mCurrentDownload); + - /// 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); + } + + } diff --cc src/com/owncloud/android/files/services/FileObserverService.java index d4bf24d0,a01446a9..fd1412c1 --- a/src/com/owncloud/android/files/services/FileObserverService.java +++ b/src/com/owncloud/android/files/services/FileObserverService.java @@@ -40,7 -40,7 +40,6 @@@ import android.content.IntentFilter import android.database.Cursor; import android.os.Binder; import android.os.IBinder; --import android.util.Log; public class FileObserverService extends Service { diff --cc src/com/owncloud/android/files/services/FileUploader.java index 04044e3e,a3c76ebc..78660986 --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploader.java @@@ -20,9 -19,10 +19,11 @@@ 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; @@@ -31,30 -31,8 +32,29 @@@ import org.apache.http.HttpStatus import org.apache.jackrabbit.webdav.MultiStatus; import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; +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.files.InstantUploadBroadcastReceiver; +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; @@@ -67,12 -45,34 +67,19 @@@ import android.os.IBinder 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 { @@@ -390,60 -482,50 +489,61 @@@ */ 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 { + /// 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( InstantUploadBroadcastReceiver.INSTANT_UPLOAD_DIR, ++ 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); - + } - + } /** @@@ -642,73 -754,89 +772,103 @@@ 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); diff --cc src/com/owncloud/android/location/LocationServiceLauncherReciever.java index a2b131f8,6bbb9ee4..a974c565 --- a/src/com/owncloud/android/location/LocationServiceLauncherReciever.java +++ b/src/com/owncloud/android/location/LocationServiceLauncherReciever.java @@@ -25,7 -26,7 +26,6 @@@ import android.content.Context import android.content.Intent; import android.content.SharedPreferences; import android.preference.PreferenceManager; --import android.util.Log; public class LocationServiceLauncherReciever extends BroadcastReceiver { diff --cc src/com/owncloud/android/location/LocationUpdateService.java index 1b6b28a7,f2eab984..7fe2ee9f --- a/src/com/owncloud/android/location/LocationUpdateService.java +++ b/src/com/owncloud/android/location/LocationUpdateService.java @@@ -28,9 -27,10 +27,9 @@@ import android.location.LocationManager 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 diff --cc src/com/owncloud/android/network/AdvancedSslSocketFactory.java index c206ca7c,3f1d8ae5..da9a0ffc --- a/src/com/owncloud/android/network/AdvancedSslSocketFactory.java +++ b/src/com/owncloud/android/network/AdvancedSslSocketFactory.java @@@ -40,8 -39,10 +39,8 @@@ import org.apache.commons.httpclient.pa 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. diff --cc src/com/owncloud/android/network/AdvancedX509TrustManager.java index d3c79246,578da185..ad4feb37 --- a/src/com/owncloud/android/network/AdvancedX509TrustManager.java +++ b/src/com/owncloud/android/network/AdvancedX509TrustManager.java @@@ -32,8 -31,10 +31,8 @@@ import javax.net.ssl.TrustManager 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 */ diff --cc src/com/owncloud/android/network/BearerAuthScheme.java index 7739822f,00000000..a4267340 mode 100644,000000..100644 --- a/src/com/owncloud/android/network/BearerAuthScheme.java +++ b/src/com/owncloud/android/network/BearerAuthScheme.java @@@ -1,269 -1,0 +1,268 @@@ +/* 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 as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. ++ * it under the terms of the GNU General Public License version 2, ++ * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.owncloud.android.network; + +import java.util.Map; + +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.auth.AuthChallengeParser; +import org.apache.commons.httpclient.auth.AuthScheme; +import org.apache.commons.httpclient.auth.AuthenticationException; +import org.apache.commons.httpclient.auth.InvalidCredentialsException; +import org.apache.commons.httpclient.auth.MalformedChallengeException; + - import android.util.Log; ++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, String, String)"); ++ 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 BearerScheme.authenticate(Credentials, HttpMethod)"); ++ 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.d(TAG, "enter BearerAuthScheme.authenticate(BearerCredentials, String)"); ++ Log_OC.d(TAG, "enter BearerAuthScheme.authenticate(BearerCredentials, String)"); + + if (credentials == null) { + throw new IllegalArgumentException("Credentials may not be null"); + } + if (charset == null || charset.length() == 0) { + throw new IllegalArgumentException("charset may not be null or empty"); + } + StringBuffer buffer = new StringBuffer(); + buffer.append(credentials.getAccessToken()); + + //return "Bearer " + EncodingUtil.getAsciiString(EncodingUtil.getBytes(buffer.toString(), charset)); + return "Bearer " + buffer.toString(); + } + + /** + * Returns a String identifying the authentication challenge. This is + * used, in combination with the host and port to determine if + * authorization has already been attempted or not. Schemes which + * require multiple requests to complete the authentication should + * return a different value for each stage in the request. + * + * Additionally, the ID should take into account any changes to the + * authentication challenge and return a different value when appropriate. + * For example when the realm changes in basic authentication it should be + * considered a different authentication attempt and a different value should + * be returned. + * + * This method simply returns the realm for the challenge. + * + * @return String a String identifying the authentication challenge. + * + * @deprecated no longer used + */ + @Override + public String getID() { + return getRealm(); + } + + /** + * Returns authentication parameter with the given name, if available. + * + * @param name The name of the parameter to be returned + * + * @return The parameter with the given name + */ + @Override + public String getParameter(String name) { + if (name == null) { + throw new IllegalArgumentException("Parameter name may not be null"); + } + if (mParams == null) { + return null; + } + return (String) mParams.get(name.toLowerCase()); + } + + /** + * Returns authentication realm. The realm may not be null. + * + * @return The authentication realm + */ + @Override + public String getRealm() { + return getParameter("realm"); + } + +} diff --cc src/com/owncloud/android/network/BearerCredentials.java index 59596e83,00000000..50799b02 mode 100644,000000..100644 --- a/src/com/owncloud/android/network/BearerCredentials.java +++ b/src/com/owncloud/android/network/BearerCredentials.java @@@ -1,98 -1,0 +1,97 @@@ +/* ownCloud Android client application + * Copyright (C) 2012 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. ++ * it under the terms of the GNU General Public License version 2, ++ * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.owncloud.android.network; + +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.util.LangUtils; + +/** + * Bearer token {@link Credentials} + * + * @author David A. Velasco + */ +public class BearerCredentials implements Credentials { + + + private String mAccessToken; + + + /** + * The constructor with the bearer token + * + * @param token The bearer token + */ + public BearerCredentials(String token) { + /*if (token == null) { + throw new IllegalArgumentException("Bearer token may not be null"); + }*/ + mAccessToken = (token == null) ? "" : token; + } + + + /** + * Returns the access token + * + * @return The access token + */ + public String getAccessToken() { + return mAccessToken; + } + + + /** + * Get this object string. + * + * @return The access token + */ + public String toString() { + return mAccessToken; + } + + /** + * Does a hash of the access token. + * + * @return The hash code of the access token + */ + public int hashCode() { + int hash = LangUtils.HASH_SEED; + hash = LangUtils.hashCode(hash, mAccessToken); + return hash; + } + + /** + * These credentials are assumed equal if accessToken is the same. + * + * @param o The other object to compare with. + * + * @return 'True' if the object is equivalent. + */ + public boolean equals(Object o) { + if (o == null) return false; + if (this == o) return true; + if (this.getClass().equals(o.getClass())) { + BearerCredentials that = (BearerCredentials) o; + if (LangUtils.equals(mAccessToken, that.mAccessToken)) { + return true; + } + } + return false; + } + +} + diff --cc src/com/owncloud/android/network/OwnCloudClientUtils.java index 7b2b64fd,02ff3f03..5d852d87 --- a/src/com/owncloud/android/network/OwnCloudClientUtils.java +++ b/src/com/owncloud/android/network/OwnCloudClientUtils.java @@@ -38,20 -37,15 +37,20 @@@ import org.apache.http.conn.ssl.Browser 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 { @@@ -76,31 -70,21 +75,31 @@@ /** * 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; } @@@ -130,8 -108,8 +129,7 @@@ return client; } - - /** * Creates a WebdavClient to access a URL and sets the desired parameters for ownCloud client connections. * @@@ -140,7 -118,7 +138,7 @@@ * @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 { diff --cc src/com/owncloud/android/operations/ChunkedUploadFileOperation.java index c01ee46b,b4f440fd..697c154e --- a/src/com/owncloud/android/operations/ChunkedUploadFileOperation.java +++ b/src/com/owncloud/android/operations/ChunkedUploadFileOperation.java @@@ -28,10 -27,12 +27,11 @@@ import java.util.Random import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.methods.PutMethod; + import com.owncloud.android.Log_OC; import com.owncloud.android.datamodel.OCFile; + import com.owncloud.android.network.ProgressiveDataTransferer; import android.accounts.Account; --import android.util.Log; import eu.alefzero.webdav.ChunkFromFileChannelRequestEntity; import eu.alefzero.webdav.WebdavClient; diff --cc src/com/owncloud/android/operations/CreateFolderOperation.java index 4d42478a,00000000..5965db32 mode 100644,000000..100644 --- a/src/com/owncloud/android/operations/CreateFolderOperation.java +++ b/src/com/owncloud/android/operations/CreateFolderOperation.java @@@ -1,96 -1,0 +1,94 @@@ +/* 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 as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. ++ * it under the terms of the GNU General Public License version 2, ++ * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.owncloud.android.operations; + +import org.apache.jackrabbit.webdav.client.methods.MkColMethod; + ++import com.owncloud.android.Log_OC; +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.OCFile; + - import android.util.Log; - +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.d(TAG, "Create directory " + mRemotePath + ": " + result.getLogMessage()); ++ Log_OC.d(TAG, "Create directory " + mRemotePath + ": " + result.getLogMessage()); + client.exhaustResponse(mkcol.getResponseBodyAsStream()); + + } catch (Exception e) { + result = new RemoteOperationResult(e); - Log.e(TAG, "Create directory " + mRemotePath + ": " + result.getLogMessage(), e); ++ Log_OC.e(TAG, "Create directory " + mRemotePath + ": " + result.getLogMessage(), e); + + } finally { + if (mkcol != null) + mkcol.releaseConnection(); + } + return result; + } + +} diff --cc src/com/owncloud/android/operations/DownloadFileOperation.java index 75bf9234,cb4355f5..5745b973 --- a/src/com/owncloud/android/operations/DownloadFileOperation.java +++ b/src/com/owncloud/android/operations/DownloadFileOperation.java @@@ -42,7 -42,7 +42,6 @@@ import eu.alefzero.webdav.OnDatatransfe import eu.alefzero.webdav.WebdavClient; import eu.alefzero.webdav.WebdavUtils; import android.accounts.Account; --import android.util.Log; import android.webkit.MimeTypeMap; /** diff --cc src/com/owncloud/android/operations/ExistenceCheckOperation.java index d678ac34,00000000..10939865 mode 100644,000000..100644 --- a/src/com/owncloud/android/operations/ExistenceCheckOperation.java +++ b/src/com/owncloud/android/operations/ExistenceCheckOperation.java @@@ -1,94 -1,0 +1,94 @@@ +/* 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 as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. ++ * it under the terms of the GNU General Public License version 2, ++ * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.owncloud.android.operations; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.HeadMethod; + ++import com.owncloud.android.Log_OC; ++ +import eu.alefzero.webdav.WebdavClient; +import android.content.Context; +import android.net.ConnectivityManager; - import android.util.Log; + +/** + * 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.d(TAG, "Existence check for " + client.getBaseUri() + mPath + " targeting for " + (mSuccessIfAbsent ? " absence " : " existence ") + "finished with HTTP status " + status + (!success?"(FAIL)":"")); ++ 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.e(TAG, "Existence check for " + client.getBaseUri() + mPath + " targeting for " + (mSuccessIfAbsent ? " absence " : " existence ") + ": " + result.getLogMessage(), result.getException()); ++ 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(); + } + + +} diff --cc src/com/owncloud/android/operations/OAuth2GetAccessToken.java index b64ebced,00000000..6d43caf1 mode 100644,000000..100644 --- a/src/com/owncloud/android/operations/OAuth2GetAccessToken.java +++ b/src/com/owncloud/android/operations/OAuth2GetAccessToken.java @@@ -1,175 -1,0 +1,174 @@@ +package com.owncloud.android.operations; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.NameValuePair; +import org.json.JSONException; +import org.json.JSONObject; + ++import com.owncloud.android.Log_OC; +import com.owncloud.android.authentication.OAuth2Constants; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; + - import android.util.Log; - +import eu.alefzero.webdav.WebdavClient; + +public class OAuth2GetAccessToken extends RemoteOperation { + + private static final String TAG = OAuth2GetAccessToken.class.getSimpleName(); + + private String mClientId; + private String mRedirectUri; + private String mGrantType; + + private String mOAuth2AuthorizationResponse; + private Map mOAuth2ParsedAuthorizationResponse; + private Map mResultTokenMap; + + + public OAuth2GetAccessToken(String clientId, String redirectUri, String grantType, String oAuth2AuthorizationResponse) { + mClientId = clientId; + mRedirectUri = redirectUri; + mGrantType = grantType; + mOAuth2AuthorizationResponse = oAuth2AuthorizationResponse; + mOAuth2ParsedAuthorizationResponse = new HashMap(); + mResultTokenMap = null; + } + + + public Map getOauth2AutorizationResponse() { + return mOAuth2ParsedAuthorizationResponse; + } + + public Map getResultTokenMap() { + return mResultTokenMap; + } + + @Override + protected RemoteOperationResult run(WebdavClient client) { + RemoteOperationResult result = null; + PostMethod postMethod = null; + + try { + parseAuthorizationResponse(); + if (mOAuth2ParsedAuthorizationResponse.keySet().contains(OAuth2Constants.KEY_ERROR)) { + if (OAuth2Constants.VALUE_ERROR_ACCESS_DENIED.equals(mOAuth2ParsedAuthorizationResponse.get(OAuth2Constants.KEY_ERROR))) { + result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR_ACCESS_DENIED); + } else { + result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); + } + } + + if (result == null) { + NameValuePair[] nameValuePairs = new NameValuePair[4]; + nameValuePairs[0] = new NameValuePair(OAuth2Constants.KEY_GRANT_TYPE, mGrantType); + nameValuePairs[1] = new NameValuePair(OAuth2Constants.KEY_CODE, mOAuth2ParsedAuthorizationResponse.get(OAuth2Constants.KEY_CODE)); + nameValuePairs[2] = new NameValuePair(OAuth2Constants.KEY_REDIRECT_URI, mRedirectUri); + nameValuePairs[3] = new NameValuePair(OAuth2Constants.KEY_CLIENT_ID, mClientId); + //nameValuePairs[4] = new NameValuePair(OAuth2Constants.KEY_SCOPE, mOAuth2ParsedAuthorizationResponse.get(OAuth2Constants.KEY_SCOPE)); + + postMethod = new PostMethod(client.getBaseUri().toString()); + postMethod.setRequestBody(nameValuePairs); + int status = client.executeMethod(postMethod); + + String response = postMethod.getResponseBodyAsString(); + if (response != null && response.length() > 0) { + JSONObject tokenJson = new JSONObject(response); + parseAccessTokenResult(tokenJson); + if (mResultTokenMap.get(OAuth2Constants.KEY_ERROR) != null || mResultTokenMap.get(OAuth2Constants.KEY_ACCESS_TOKEN) == null) { + result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); + + } else { + result = new RemoteOperationResult(true, status); + } + + } 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.i(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage()); ++ 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() + ": " + result.getLogMessage(), result.getException()); ++ 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() + ": " + ((mResultTokenMap != null) ? mResultTokenMap.get(OAuth2Constants.KEY_ERROR) : "NULL")); ++ 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.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() + ": " + 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.v(TAG, "[" + i + "," + j + "] = " + p); ++ Log_OC.v(TAG, "[" + i + "," + j + "] = " + p); + j++; + } + i++; + } + } + + + private void parseAccessTokenResult (JSONObject tokenJson) throws JSONException { + mResultTokenMap = new HashMap(); + + if (tokenJson.has(OAuth2Constants.KEY_ACCESS_TOKEN)) { + mResultTokenMap.put(OAuth2Constants.KEY_ACCESS_TOKEN, tokenJson.getString(OAuth2Constants.KEY_ACCESS_TOKEN)); + } + if (tokenJson.has(OAuth2Constants.KEY_TOKEN_TYPE)) { + mResultTokenMap.put(OAuth2Constants.KEY_TOKEN_TYPE, tokenJson.getString(OAuth2Constants.KEY_TOKEN_TYPE)); + } + if (tokenJson.has(OAuth2Constants.KEY_EXPIRES_IN)) { + mResultTokenMap.put(OAuth2Constants.KEY_EXPIRES_IN, tokenJson.getString(OAuth2Constants.KEY_EXPIRES_IN)); + } + if (tokenJson.has(OAuth2Constants.KEY_REFRESH_TOKEN)) { + mResultTokenMap.put(OAuth2Constants.KEY_REFRESH_TOKEN, tokenJson.getString(OAuth2Constants.KEY_REFRESH_TOKEN)); + } + if (tokenJson.has(OAuth2Constants.KEY_SCOPE)) { + mResultTokenMap.put(OAuth2Constants.KEY_SCOPE, tokenJson.getString(OAuth2Constants.KEY_SCOPE)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR, tokenJson.getString(OAuth2Constants.KEY_ERROR)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR_DESCRIPTION)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR_DESCRIPTION, tokenJson.getString(OAuth2Constants.KEY_ERROR_DESCRIPTION)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR_URI)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR_URI, tokenJson.getString(OAuth2Constants.KEY_ERROR_URI)); + } + } + +} diff --cc src/com/owncloud/android/operations/OwnCloudServerCheckOperation.java index 7132c53f,00000000..1afcf6ed mode 100644,000000..100644 --- a/src/com/owncloud/android/operations/OwnCloudServerCheckOperation.java +++ b/src/com/owncloud/android/operations/OwnCloudServerCheckOperation.java @@@ -1,138 -1,0 +1,137 @@@ +/* 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 as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. ++ * it under the terms of the GNU General Public License version 2, ++ * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.owncloud.android.operations; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.GetMethod; +import org.json.JSONException; +import org.json.JSONObject; + +import com.owncloud.android.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; - import android.util.Log; + +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.i(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage()); ++ Log_OC.i(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage()); + + } else if (mLatestResult.getException() != null) { - Log.e(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage(), mLatestResult.getException()); ++ Log_OC.e(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage(), mLatestResult.getException()); + + } else { - Log.e(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage()); ++ 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.d(TAG, "establishing secure connection failed, trying non secure connection"); ++ Log_OC.d(TAG, "establishing secure connection failed, trying non secure connection"); + client.setBaseUri(Uri.parse("http://" + mUrl + AccountUtils.STATUS_PATH)); + tryConnection(client, "http://" + mUrl + AccountUtils.STATUS_PATH); + } + } + return mLatestResult; + } + +} diff --cc src/com/owncloud/android/operations/RemoteOperation.java index e7d40347,6d990448..711a72b0 --- a/src/com/owncloud/android/operations/RemoteOperation.java +++ b/src/com/owncloud/android/operations/RemoteOperation.java @@@ -17,22 -16,7 +16,22 @@@ */ 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; @@@ -71,34 -44,6 +70,34 @@@ public abstract class RemoteOperation i */ protected abstract RemoteOperationResult run(WebdavClient client); + + /** + * Synchronously executes the remote operation on the received ownCloud account. + * + * Do not call this method from the main thread. + * + * This method should be used whenever an ownCloud account is available, instead of {@link #execute(WebdavClient)}. + * + * @param account ownCloud account in remote ownCloud server to reach during the execution of the operation. + * @param context Android context for the component calling the method. + * @return Result of the operation. + */ + public final RemoteOperationResult execute(Account account, Context context) { + if (account == null) + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Account"); + if (context == null) + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Context"); + mAccount = account; + mContext = context.getApplicationContext(); + try { + mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext); + } catch (Exception e) { - Log.e(TAG, "Error while trying to access to " + mAccount.name, 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 @@@ -214,51 -118,8 +213,51 @@@ */ @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 diff --cc src/com/owncloud/android/operations/RemoteOperationResult.java index 14cf78fc,db1771ba..16b47798 --- a/src/com/owncloud/android/operations/RemoteOperationResult.java +++ b/src/com/owncloud/android/operations/RemoteOperationResult.java @@@ -33,9 -32,11 +32,9 @@@ import org.apache.commons.httpclient.Ht 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. * @@@ -44,37 -46,13 +44,40 @@@ * @author David A. Velasco */ public class RemoteOperationResult implements Serializable { - + /** Generated - should be refreshed every time the class changes!! */ private static final long serialVersionUID = -7805531062432602444L; + + private static final String TAG = "RemoteOperationResult"; - - public enum ResultCode { - OK, OK_SSL, OK_NO_SSL, UNHANDLED_HTTP_CODE, UNAUTHORIZED, FILE_NOT_FOUND, INSTANCE_NOT_CONFIGURED, UNKNOWN_ERROR, WRONG_CONNECTION, TIMEOUT, INCORRECT_ADDRESS, HOST_NOT_AVAILABLE, NO_NETWORK_CONNECTION, SSL_ERROR, SSL_RECOVERABLE_PEER_UNVERIFIED, BAD_OC_VERSION, CANCELLED, INVALID_LOCAL_FILE_NAME, INVALID_OVERWRITE, CONFLICT, SYNC_CONFLICT, LOCAL_STORAGE_FULL, LOCAL_STORAGE_NOT_MOVED, LOCAL_STORAGE_NOT_COPIED, QUOTA_EXCEEDED ++ + 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 ++ OAUTH2_ERROR_ACCESS_DENIED, ++ QUOTA_EXCEEDED } private boolean mSuccess = false; diff --cc src/com/owncloud/android/operations/RemoveFileOperation.java index 80f40977,dc699484..8348b72f --- a/src/com/owncloud/android/operations/RemoveFileOperation.java +++ b/src/com/owncloud/android/operations/RemoveFileOperation.java @@@ -21,8 -20,9 +20,7 @@@ package com.owncloud.android.operations 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; diff --cc src/com/owncloud/android/operations/RenameFileOperation.java index b9806575,7ea5cf94..1c636fba --- a/src/com/owncloud/android/operations/RenameFileOperation.java +++ b/src/com/owncloud/android/operations/RenameFileOperation.java @@@ -25,8 -24,9 +24,8 @@@ import org.apache.jackrabbit.webdav.cli //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; diff --cc src/com/owncloud/android/operations/SynchronizeFileOperation.java index 606017aa,07093103..b0f2ce26 --- a/src/com/owncloud/android/operations/SynchronizeFileOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFileOperation.java @@@ -26,8 -25,9 +25,8 @@@ import org.apache.jackrabbit.webdav.cli 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; diff --cc src/com/owncloud/android/operations/SynchronizeFolderOperation.java index 977460bf,843da114..3c1a64f6 --- a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java @@@ -35,8 -34,9 +34,8 @@@ import org.apache.jackrabbit.webdav.cli 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; diff --cc src/com/owncloud/android/operations/UpdateOCVersionOperation.java index 02f79c95,f9e4acb3..dad57172 --- a/src/com/owncloud/android/operations/UpdateOCVersionOperation.java +++ b/src/com/owncloud/android/operations/UpdateOCVersionOperation.java @@@ -26,10 -25,11 +25,10 @@@ import org.json.JSONObject 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.authentication.AccountAuthenticator; + import com.owncloud.android.Log_OC; -import com.owncloud.android.authenticator.AccountAuthenticator; import com.owncloud.android.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.utils.OwnCloudVersion; diff --cc src/com/owncloud/android/providers/FileContentProvider.java index 5e54341b,f9e0a480..9b032434 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@@ -1,289 -1,289 +1,288 @@@ - /* 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 as published by - * the Free Software Foundation, either version 2 of the License, or - * (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 details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - - package com.owncloud.android.providers; - - import java.util.HashMap; - - import com.owncloud.android.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 mProjectionMap; - static { - mProjectionMap = new HashMap(); - mProjectionMap.put(ProviderTableMeta._ID, ProviderTableMeta._ID); - mProjectionMap.put(ProviderTableMeta.FILE_PARENT, - ProviderTableMeta.FILE_PARENT); - mProjectionMap.put(ProviderTableMeta.FILE_PATH, - ProviderTableMeta.FILE_PATH); - mProjectionMap.put(ProviderTableMeta.FILE_NAME, - ProviderTableMeta.FILE_NAME); - mProjectionMap.put(ProviderTableMeta.FILE_CREATION, - ProviderTableMeta.FILE_CREATION); - mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED, - ProviderTableMeta.FILE_MODIFIED); - mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, - ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA); - mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_LENGTH, - ProviderTableMeta.FILE_CONTENT_LENGTH); - mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_TYPE, - ProviderTableMeta.FILE_CONTENT_TYPE); - mProjectionMap.put(ProviderTableMeta.FILE_STORAGE_PATH, - ProviderTableMeta.FILE_STORAGE_PATH); - mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, - ProviderTableMeta.FILE_LAST_SYNC_DATE); - mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, - ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA); - mProjectionMap.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, - ProviderTableMeta.FILE_KEEP_IN_SYNC); - mProjectionMap.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, - ProviderTableMeta.FILE_ACCOUNT_OWNER); - } - - private static final int SINGLE_FILE = 1; - private static final int DIRECTORY = 2; - private static final int ROOT_DIRECTORY = 3; - private 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.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.i("SQL", "Entering in onUpgrade"); - boolean upgraded = false; - if (oldVersion == 1 && newVersion >= 2) { - Log.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.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.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.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); - } - - } - - } + /* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + package com.owncloud.android.providers; + + import java.util.HashMap; + + import com.owncloud.android.Log_OC; + import com.owncloud.android.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 mProjectionMap; + static { + mProjectionMap = new HashMap(); + mProjectionMap.put(ProviderTableMeta._ID, ProviderTableMeta._ID); + mProjectionMap.put(ProviderTableMeta.FILE_PARENT, + ProviderTableMeta.FILE_PARENT); + mProjectionMap.put(ProviderTableMeta.FILE_PATH, + ProviderTableMeta.FILE_PATH); + mProjectionMap.put(ProviderTableMeta.FILE_NAME, + ProviderTableMeta.FILE_NAME); + mProjectionMap.put(ProviderTableMeta.FILE_CREATION, + ProviderTableMeta.FILE_CREATION); + mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED, + ProviderTableMeta.FILE_MODIFIED); + mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA); + mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_LENGTH, + ProviderTableMeta.FILE_CONTENT_LENGTH); + mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_TYPE, + ProviderTableMeta.FILE_CONTENT_TYPE); + mProjectionMap.put(ProviderTableMeta.FILE_STORAGE_PATH, + ProviderTableMeta.FILE_STORAGE_PATH); + mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, + ProviderTableMeta.FILE_LAST_SYNC_DATE); + mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA); + mProjectionMap.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, + ProviderTableMeta.FILE_KEEP_IN_SYNC); + mProjectionMap.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, + ProviderTableMeta.FILE_ACCOUNT_OWNER); + } + + private static final int SINGLE_FILE = 1; + private static final int DIRECTORY = 2; + private static final int ROOT_DIRECTORY = 3; + private 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); + } + + } + + } diff --cc src/com/owncloud/android/syncadapter/FileSyncAdapter.java index c3370f2d,b3a42ea3..ec8750c0 --- a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java @@@ -1,394 -1,370 +1,389 @@@ - /* 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 as published by - * the Free Software Foundation, either version 2 of the License, or - * (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 details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - - package com.owncloud.android.syncadapter; - - import java.io.IOException; - import java.util.ArrayList; - import java.util.HashMap; - import java.util.List; - import java.util.Map; - - import org.apache.jackrabbit.webdav.DavException; - - import com.owncloud.android.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 - */ - public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { - - private final static String TAG = "FileSyncAdapter"; - - /** - * Maximum number of failed folder synchronizations that are supported before finishing the synchronization operation - */ - private static final int MAX_FAILED_RESULTS = 3; - - private long mCurrentSyncTime; - private boolean mCancellation; - private boolean mIsManualSync; - private int mFailedResultsCounter; - private RemoteOperationResult mLastFailedResult; - private SyncResult mSyncResult; - private int mConflictsFound; - private int mFailsInFavouritesFound; - private Map mForgottenLocalFiles; - - - public FileSyncAdapter(Context context, boolean autoInitialize) { - super(context, autoInitialize); - } - - /** - * {@inheritDoc} - */ - @Override - public synchronized void onPerformSync(Account account, Bundle extras, - String authority, ContentProviderClient provider, - SyncResult syncResult) { - - mCancellation = false; - mIsManualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); - mFailedResultsCounter = 0; - mLastFailedResult = null; - mConflictsFound = 0; - mFailsInFavouritesFound = 0; - mForgottenLocalFiles = new HashMap(); - mSyncResult = syncResult; - mSyncResult.fullSyncRequested = false; - mSyncResult.delayUntil = 60*60*24; // sync after 24h - - this.setAccount(account); - this.setContentProvider(provider); - this.setStorageManager(new FileDataStorageManager(account, getContentProvider())); - try { - this.initClientForCurrentAccount(); - } catch (IOException e) { - /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again - mSyncResult.tooManyRetries = true; - notifyFailedSynchronization(); - return; - } catch (AccountsException e) { - /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again - mSyncResult.tooManyRetries = true; - notifyFailedSynchronization(); - return; - } - - Log.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.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.d(TAG, "Synchronization of " + getAccount().name + " has been requested to cancel"); - mCancellation = true; - super.onSyncCanceled(); - } - - - /** - * Updates the locally stored version value of the ownCloud server - */ - private void updateOCVersion() { - UpdateOCVersionOperation update = new UpdateOCVersionOperation(getAccount(), getContext()); - RemoteOperationResult result = update.execute(getClient()); - if (!result.isSuccess()) { - mLastFailedResult = result; - } - } - - - - /** - * Synchronize the properties of files and folders contained in a remote folder given by remotePath. - * - * @param remotePath Remote path to the folder to synchronize. - * @param parentId Database Id of the folder to synchronize. - */ - private void fetchData(String remotePath, long parentId) { - - if (mFailedResultsCounter > MAX_FAILED_RESULTS || isFinisher(mLastFailedResult)) - return; - - // perform folder synchronization - SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation( remotePath, - mCurrentSyncTime, - parentId, - getStorageManager(), - getAccount(), - getContext() - ); - RemoteOperationResult result = synchFolderOp.execute(getClient()); - - - // synchronized folder -> notice to UI - ALWAYS, although !result.isSuccess - sendStickyBroadcast(true, remotePath, null); - - if (result.isSuccess() || result.getCode() == ResultCode.SYNC_CONFLICT) { - - if (result.getCode() == ResultCode.SYNC_CONFLICT) { - mConflictsFound += synchFolderOp.getConflictsFound(); - mFailsInFavouritesFound += synchFolderOp.getFailsInFavouritesFound(); - } - if (synchFolderOp.getForgottenLocalFiles().size() > 0) { - mForgottenLocalFiles.putAll(synchFolderOp.getForgottenLocalFiles()); - } - // synchronize children folders - List children = synchFolderOp.getChildren(); - fetchChildren(children); // beware of the 'hidden' recursion here! - - } 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.UNAUTHORIZED) || - code.equals(RemoteOperationResult.ResultCode.BAD_OC_VERSION) || - code.equals(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED)); - } - return false; - } - - /** - * Synchronize data of folders in the list of received files - * - * @param files Files to recursively fetch - */ - private void fetchChildren(List files) { - int i; - for (i=0; i < files.size() && !mCancellation; i++) { - OCFile newFile = files.get(i); - if (newFile.isDirectory()) { - fetchData(newFile.getRemotePath(), newFile.getFileId()); - } - } - if (mCancellation && i 0) { - Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_fail_in_favourites_ticker), System.currentTimeMillis()); - notification.flags |= Notification.FLAG_AUTO_CANCEL; - // TODO put something smart in the contentIntent below - notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); - notification.setLatestEventInfo(getContext().getApplicationContext(), - getContext().getString(R.string.sync_fail_in_favourites_ticker), - String.format(getContext().getString(R.string.sync_fail_in_favourites_content), mFailedResultsCounter + mConflictsFound, mConflictsFound), - notification.contentIntent); - ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_in_favourites_ticker, notification); - - } else { - Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_conflicts_in_favourites_ticker), System.currentTimeMillis()); - notification.flags |= Notification.FLAG_AUTO_CANCEL; - // TODO put something smart in the contentIntent below - notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); - notification.setLatestEventInfo(getContext().getApplicationContext(), - getContext().getString(R.string.sync_conflicts_in_favourites_ticker), - String.format(getContext().getString(R.string.sync_conflicts_in_favourites_content), mConflictsFound), - notification.contentIntent); - ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_conflicts_in_favourites_ticker, notification); - } - } - - - /** - * Notifies the user about local copies of files out of the ownCloud local directory that were 'forgotten' because - * copying them inside the ownCloud local directory was not possible. - * - * We don't want links to files out of the ownCloud local directory (foreign files) anymore. It's easy to have - * synchronization problems if a local file is linked to more than one remote file. - * - * We won't consider a synchronization as failed when foreign files can not be copied to the ownCloud local directory. - */ - private void notifyForgottenLocalFiles() { - Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_foreign_files_forgotten_ticker), System.currentTimeMillis()); - notification.flags |= Notification.FLAG_AUTO_CANCEL; - - /// includes a pending intent in the notification showing a more detailed explanation - Intent explanationIntent = new Intent(getContext(), ErrorsWhileCopyingHandlerActivity.class); - explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_ACCOUNT, getAccount()); - ArrayList remotePaths = new ArrayList(); - ArrayList localPaths = new ArrayList(); - remotePaths.addAll(mForgottenLocalFiles.keySet()); - localPaths.addAll(mForgottenLocalFiles.values()); - explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_LOCAL_PATHS, localPaths); - explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_REMOTE_PATHS, remotePaths); - explanationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - - notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), explanationIntent, 0); - notification.setLatestEventInfo(getContext().getApplicationContext(), - getContext().getString(R.string.sync_foreign_files_forgotten_ticker), - String.format(getContext().getString(R.string.sync_foreign_files_forgotten_content), mForgottenLocalFiles.size(), getContext().getString(R.string.app_name)), - notification.contentIntent); - ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_foreign_files_forgotten_ticker, notification); - - } - - - } + /* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + package com.owncloud.android.syncadapter; + + import java.io.IOException; -import java.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 mForgottenLocalFiles; + + + public FileSyncAdapter(Context context, boolean autoInitialize) { + super(context, autoInitialize); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void onPerformSync(Account account, Bundle extras, + String authority, ContentProviderClient provider, + SyncResult syncResult) { + + mCancellation = false; + mIsManualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); + mFailedResultsCounter = 0; + mLastFailedResult = null; + mConflictsFound = 0; + mFailsInFavouritesFound = 0; + mForgottenLocalFiles = new HashMap(); + mSyncResult = syncResult; + mSyncResult.fullSyncRequested = false; + mSyncResult.delayUntil = 60*60*24; // sync after 24h + + this.setAccount(account); + this.setContentProvider(provider); + this.setStorageManager(new FileDataStorageManager(account, getContentProvider())); + try { + this.initClientForCurrentAccount(); - } catch (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 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 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 0) { + Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_fail_in_favourites_ticker), System.currentTimeMillis()); + notification.flags |= Notification.FLAG_AUTO_CANCEL; + // TODO put something smart in the contentIntent below + notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); + notification.setLatestEventInfo(getContext().getApplicationContext(), + getContext().getString(R.string.sync_fail_in_favourites_ticker), + String.format(getContext().getString(R.string.sync_fail_in_favourites_content), mFailedResultsCounter + mConflictsFound, mConflictsFound), + notification.contentIntent); + ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_in_favourites_ticker, notification); + + } else { + Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_conflicts_in_favourites_ticker), System.currentTimeMillis()); + notification.flags |= Notification.FLAG_AUTO_CANCEL; + // TODO put something smart in the contentIntent below + notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); + notification.setLatestEventInfo(getContext().getApplicationContext(), + getContext().getString(R.string.sync_conflicts_in_favourites_ticker), + String.format(getContext().getString(R.string.sync_conflicts_in_favourites_content), mConflictsFound), + notification.contentIntent); + ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_conflicts_in_favourites_ticker, notification); + } + } - + + /** + * Notifies the user about local copies of files out of the ownCloud local directory that were 'forgotten' because + * copying them inside the ownCloud local directory was not possible. + * + * We don't want links to files out of the ownCloud local directory (foreign files) anymore. It's easy to have + * synchronization problems if a local file is linked to more than one remote file. + * + * We won't consider a synchronization as failed when foreign files can not be copied to the ownCloud local directory. + */ + private void notifyForgottenLocalFiles() { + Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_foreign_files_forgotten_ticker), System.currentTimeMillis()); + notification.flags |= Notification.FLAG_AUTO_CANCEL; + + /// includes a pending intent in the notification showing a more detailed explanation + Intent explanationIntent = new Intent(getContext(), ErrorsWhileCopyingHandlerActivity.class); + explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_ACCOUNT, getAccount()); + ArrayList remotePaths = new ArrayList(); + ArrayList localPaths = new ArrayList(); + remotePaths.addAll(mForgottenLocalFiles.keySet()); + localPaths.addAll(mForgottenLocalFiles.values()); + explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_LOCAL_PATHS, localPaths); + explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_REMOTE_PATHS, remotePaths); + explanationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + + notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), explanationIntent, 0); + notification.setLatestEventInfo(getContext().getApplicationContext(), + getContext().getString(R.string.sync_foreign_files_forgotten_ticker), + String.format(getContext().getString(R.string.sync_foreign_files_forgotten_content), mForgottenLocalFiles.size(), getContext().getString(R.string.app_name)), + notification.contentIntent); + ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_foreign_files_forgotten_ticker, notification); + + } + + + } diff --cc src/com/owncloud/android/ui/activity/AccountSelectActivity.java index 2c29e621,7a1cac51..be369dce --- a/src/com/owncloud/android/ui/activity/AccountSelectActivity.java +++ b/src/com/owncloud/android/ui/activity/AccountSelectActivity.java @@@ -33,7 -32,7 +32,6 @@@ import android.content.Context 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; @@@ -50,7 -49,8 +48,8 @@@ import com.actionbarsherlock.view.Menu import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.owncloud.android.AccountUtils; +import com.owncloud.android.authentication.AccountAuthenticator; + import com.owncloud.android.Log_OC; -import com.owncloud.android.authenticator.AccountAuthenticator; import com.owncloud.android.R; diff --cc src/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java index 0522cb85,5ac6df92..d61626c0 --- a/src/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java +++ b/src/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java @@@ -29,7 -28,7 +28,6 @@@ import android.os.Bundle 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; diff --cc src/com/owncloud/android/ui/activity/FailedUploadActivity.java index 00000000,e468ffb3..4c13fb27 mode 000000,100644..100644 --- a/src/com/owncloud/android/ui/activity/FailedUploadActivity.java +++ b/src/com/owncloud/android/ui/activity/FailedUploadActivity.java @@@ -1,0 -1,49 +1,55 @@@ ++/* ownCloud Android client application ++ * Copyright (C) 2012-2013 ownCloud Inc. ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2, ++ * as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ */ ++ + package com.owncloud.android.ui.activity; + + import android.app.Activity; + import android.os.Bundle; + import android.view.View; + import android.view.View.OnClickListener; + import android.widget.Button; + import android.widget.TextView; + + import com.owncloud.android.R; + + /** + * This Activity is used to display a detail message for failed uploads + * + * The entry-point for this activity is the 'Failed upload Notification" + * - * + * @author andomaex / Matthias Baumann - * - * 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/ + */ + public class FailedUploadActivity extends Activity { + + public static final String MESSAGE = "message"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.failed_upload_message_view); + String message = getIntent().getStringExtra(MESSAGE); + TextView textView = (TextView) findViewById(R.id.faild_upload_message); + textView.setText(message); + Button close_button = (Button) findViewById(R.id.failed_uploadactivity_close_button); + close_button.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + } + } diff --cc src/com/owncloud/android/ui/activity/FileDetailActivity.java index cd68c891,711b07ad..4cd93630 --- a/src/com/owncloud/android/ui/activity/FileDetailActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDetailActivity.java @@@ -1,230 -1,391 +1,390 @@@ - /* 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 as published by - * the Free Software Foundation, either version 2 of the License, or - * (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 details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - package com.owncloud.android.ui.activity; - - import android.accounts.Account; - import android.app.Dialog; - import android.app.ProgressDialog; - import android.content.ComponentName; - import android.content.Context; - import android.content.Intent; - import android.content.ServiceConnection; - import android.content.res.Configuration; - import android.os.Bundle; - import android.os.IBinder; - 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.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.R; - - /** - * This activity displays the details of a file like its name, its size and so - * on. - * - * @author Bartek Przybylski - * - */ - public class FileDetailActivity extends SherlockFragmentActivity implements FileDetailFragment.ContainerActivity { - - public static final int DIALOG_SHORT_WAIT = 0; - - public static final String TAG = FileDetailActivity.class.getSimpleName(); - - private boolean mConfigurationChangedToLandscape = false; - private FileDownloaderBinder mDownloaderBinder = null; - private ServiceConnection mDownloadConnection, mUploadConnection = null; - private FileUploaderBinder mUploaderBinder = null; - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // 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) { - 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); - - setContentView(R.layout.file_activity_details); - - ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); - - OCFile file = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE); - Account account = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT); - FileDetailFragment mFileDetail = new FileDetailFragment(file, account); - - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - ft.replace(R.id.fragment, mFileDetail, FileDetailFragment.FTAG); - ft.commit(); - - } else { - backToDisplayActivity(); // the 'back' won't be effective until this.onStart() and this.onResume() are completed; - } - - - } - - - /** 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.d(TAG, "Download service connected"); - mDownloaderBinder = (FileDownloaderBinder) service; - } else if (component.equals(new ComponentName(FileDetailActivity.this, FileUploader.class))) { - Log.d(TAG, "Upload service connected"); - mUploaderBinder = (FileUploaderBinder) service; - } else { - return; - } - FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (fragment != null) - fragment.updateFileDetails(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.d(TAG, "Download service disconnected"); - mDownloaderBinder = null; - } else if (component.equals(new ComponentName(FileDetailActivity.this, FileUploader.class))) { - Log.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(); - returnValue = true; - break; - default: - returnValue = super.onOptionsItemSelected(item); - } - - return returnValue; - } - - - - @Override - protected void onResume() { - - super.onResume(); - if (!mConfigurationChangedToLandscape) { - FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - fragment.updateFileDetails(false); - } - } - - - private void backToDisplayActivity() { - Intent intent = new Intent(this, FileDisplayActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(FileDetailFragment.EXTRA_FILE, getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE)); - intent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT)); - 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; - } - - } + /* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + package com.owncloud.android.ui.activity; + + import android.accounts.Account; + import android.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 ) + + } + + } diff --cc src/com/owncloud/android/ui/activity/FileDisplayActivity.java index c4cbe978,88dd728a..6fd51a9a --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@@ -1,1270 -1,1467 +1,1363 @@@ - /* 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 as published by - * the Free Software Foundation, either version 2 of the License, or - * (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 details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - - package com.owncloud.android.ui.activity; - - import java.io.File; - - import android.accounts.Account; - import android.app.AlertDialog; - import android.app.ProgressDialog; - import android.app.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.pm.PackageManager.NameNotFoundException; - import android.content.res.Resources.NotFoundException; - import android.database.Cursor; - import android.graphics.Bitmap; - import android.graphics.drawable.BitmapDrawable; - 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.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.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.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.SslValidatorDialog; - import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener; - import com.owncloud.android.ui.fragment.FileDetailFragment; - import com.owncloud.android.ui.fragment.OCFileListFragment; - - import com.owncloud.android.R; - - /** - * Displays, what files the user has available in his ownCloud. - * - * @author Bartek Przybylski - * - */ - - public class FileDisplayActivity extends SherlockFragmentActivity implements - OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener, OnSslValidatorListener, OnRemoteOperationListener { - - private ArrayAdapter 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 Handler mHandler; - - private static final int DIALOG_SETUP_ACCOUNT = 0; - private static final int DIALOG_CREATE_DIR = 1; - private static final int DIALOG_ABOUT_APP = 2; - 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"; - - - 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 static int[] mMenuIdentifiersToPatch = {R.id.about_app}; - - @Override - public void onCreate(Bundle savedInstanceState) { - Log.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, or SOME TIMES - mCurrentDir = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_FILE); - } - - 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(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) { - initFileDetailsInDualPane(); - } - - // 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(); - - Log.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.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) { - transaction.replace(R.id.file_details_container, new FileDetailFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); // empty FileDetailFragment - 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.menu, menu); - - patchHiddenAccents(menu); - - return true; - } - - /** - * Workaround for this: http://code.google.com/p/android/issues/detail?id=3974 - * - * @param menu Menu to patch - */ - private void patchHiddenAccents(Menu menu) { - for (int i = 0; i < mMenuIdentifiersToPatch.length ; i++) { - MenuItem aboutItem = menu.findItem(mMenuIdentifiersToPatch[i]); - if (aboutItem != null && aboutItem.getIcon() instanceof BitmapDrawable) { - // Clip off the bottom three (density independent) pixels of transparent padding - Bitmap original = ((BitmapDrawable) aboutItem.getIcon()).getBitmap(); - float scale = getResources().getDisplayMetrics().density; - int clippedHeight = (int) (original.getHeight() - (3 * scale)); - Bitmap scaled = Bitmap.createBitmap(original, 0, 0, original.getWidth(), clippedHeight); - aboutItem.setIcon(new BitmapDrawable(getResources(), scaled)); - } - } - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - boolean retval = true; - switch (item.getItemId()) { - case R.id.createDirectoryItem: { - showDialog(DIALOG_CREATE_DIR); - break; - } - case R.id.startSync: { - 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 R.id.about_app : { - showDialog(DIALOG_ABOUT_APP); - 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.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.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.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.e("FileDisplay", "Unexpected exception when trying to read the result of Intent.ACTION_GET_CONTENT", e); - e.printStackTrace(); - - } finally { - if (filepath == null) { - Log.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 - FileDetailFragment fileDetails = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (fileDetails != null && !fileDetails.isEmpty()) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.remove(fileDetails); - transaction.add(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); - 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.d(getClass().toString(), "onSaveInstanceState() start"); - super.onSaveInstanceState(outState); - outState.putParcelable(FileDetailFragment.EXTRA_FILE, mCurrentDir); - if (mDualPane) { - FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (fragment != null) { - OCFile file = fragment.getDisplayedFile(); - if (file != null) { - outState.putParcelable(FileDetailFragment.EXTRA_FILE, file); - } - } - } - Log.d(getClass().toString(), "onSaveInstanceState() end"); - } - - @Override - protected void onResume() { - Log.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(); - } - } - - // 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_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.d(getClass().toString(), "onResume() end"); - } - - - @Override - protected void onPause() { - Log.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.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_ABOUT_APP: { - builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.about_title)); - PackageInfo pkg; - try { - pkg = getPackageManager().getPackageInfo(getPackageName(), 0); - builder.setMessage(String.format(getString(R.string.about_message), getString(R.string.app_name), pkg.versionName)); - builder.setIcon(android.R.drawable.ic_menu_info_details); - dialog = builder.create(); - } catch (NameNotFoundException e) { - builder = null; - dialog = null; - Log.e(TAG, "Error while showing about dialog", e); - } - 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; - RemoteOperation operation = new CreateFolderOperation(path, mCurrentDir.getFileId(), mStorageManager); - operation.execute( AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this), - FileDisplayActivity.this, - FileDisplayActivity.this, - mHandler, - FileDisplayActivity.this); - - 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: { - final String [] items = { getString(R.string.actionbar_upload_files), - getString(R.string.actionbar_upload_from_apps) }; - 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); - } - } - }); - 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(); - } - - - // Custom array adapter to override text colors - private class CustomArrayAdapter extends ArrayAdapter { - - public CustomArrayAdapter(FileDisplayActivity ctx, int view) { - super(ctx, view); - } - - public View getView(int position, View convertView, ViewGroup parent) { - View v = super.getView(position, convertView, parent); - - ((TextView) v).setTextColor(getResources().getColorStateList( - android.R.color.white)); - return v; - } - - public View getDropDownView(int position, View convertView, - ViewGroup parent) { - View v = super.getDropDownView(position, convertView, parent); - - ((TextView) v).setTextColor(getResources().getColorStateList( - android.R.color.white)); - - return v; - } - - } - - private class SyncBroadcastReceiver extends BroadcastReceiver { - - /** - * {@link BroadcastReceiver} to enable syncing feedback in UI - */ - @Override - public void onReceive(Context context, Intent intent) { - boolean inProgress = intent.getBooleanExtra( - FileSyncService.IN_PROGRESS, false); - String accountName = intent - .getStringExtra(FileSyncService.ACCOUNT_NAME); - - Log.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); - 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(); - } - } - } - - } - - - /** - * Once the file download has finished -> update view - */ - private class DownloadFinishReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); - String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME); - boolean sameAccount = accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name); - boolean isDescendant = (mCurrentDir != null) && (downloadedRemotePath != null) && (downloadedRemotePath.startsWith(mCurrentDir.getRemotePath())); - if (sameAccount && isDescendant) { - OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList); - if (fileListFragment != null) { - fileListFragment.listDirectory(); - } - } - } - } - - - - - /** - * {@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 - FileDetailFragment fileDetails = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (fileDetails != null && !fileDetails.isEmpty()) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.remove(fileDetails); - transaction.add(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); - transaction.commit(); - } - } - } - - - /** - * {@inheritDoc} - */ - @Override - public void onFileClick(OCFile file) { - - // If we are on a large device -> update fragment - if (mDualPane) { - // 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' - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); - transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); - transaction.commit(); - - } else { // small or medium screen device -> new Activity - 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.d(TAG, "Download service connected"); - mDownloaderBinder = (FileDownloaderBinder) service; - } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) { - Log.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) { - FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (fragment != null) - fragment.updateFileDetails(false); - } - } - - @Override - public void onServiceDisconnected(ComponentName component) { - if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloader.class))) { - Log.d(TAG, "Download service disconnected"); - mDownloaderBinder = null; - } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) { - Log.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 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.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 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) { - FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (details != null && removedFile.equals(details.getDisplayedFile()) ) { - 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 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) { - FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (details != null && renamedFile.equals(details.getDisplayedFile()) ) { - 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) { - FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (details != null && file.equals(details.getDisplayedFile()) ) { - if (downloading || uploading) { - details.updateFileDetails(file, AccountUtils.getCurrentOwnCloudAccount(this)); - } else { - details.updateFileDetails(downloading || uploading); - } - } - } - } - - - - - - } + /* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + package com.owncloud.android.ui.activity; + + import java.io.File; + + import android.accounts.Account; + import android.app.AlertDialog; + import android.app.ProgressDialog; -import android.app.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 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(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 extends ArrayAdapter { + + public CustomArrayAdapter(FileDisplayActivity ctx, int view) { + super(ctx, view); + } + + public View getView(int position, View convertView, ViewGroup parent) { + View v = super.getView(position, convertView, parent); + + ((TextView) v).setTextColor(getResources().getColorStateList( + android.R.color.white)); + return v; + } + + public View getDropDownView(int position, View convertView, + ViewGroup parent) { + View v = super.getDropDownView(position, convertView, parent); + + ((TextView) v).setTextColor(getResources().getColorStateList( + android.R.color.white)); + + return v; + } + + } + + private class SyncBroadcastReceiver extends BroadcastReceiver { + + /** + * {@link BroadcastReceiver} to enable syncing feedback in UI + */ + @Override + public void onReceive(Context context, Intent intent) { + boolean inProgress = intent.getBooleanExtra(FileSyncService.IN_PROGRESS, false); + String accountName = intent.getStringExtra(FileSyncService.ACCOUNT_NAME); + + Log_OC.d("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); + } + } + + + } diff --cc src/com/owncloud/android/ui/activity/InstantUploadActivity.java index 00000000,8f34327b..99b0b832 mode 000000,100644..100644 --- a/src/com/owncloud/android/ui/activity/InstantUploadActivity.java +++ b/src/com/owncloud/android/ui/activity/InstantUploadActivity.java @@@ -1,0 -1,486 +1,475 @@@ + /* 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 as published by - * the Free Software Foundation, either version 3 of the License. ++ * it under the terms of the GNU General Public License version 2, ++ * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + package com.owncloud.android.ui.activity; + + import java.util.ArrayList; + import java.util.List; + + import android.accounts.Account; + import android.app.Activity; + import android.content.Intent; + import android.database.Cursor; + import android.graphics.Bitmap; + import android.graphics.BitmapFactory; + import android.os.Bundle; -import android.util.Log; + 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 - * - * 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/ + */ + public class InstantUploadActivity extends Activity { + + private static final String LOG_TAG = InstantUploadActivity.class.getSimpleName(); + private LinearLayout listView; + private static final String retry_chexbox_tag = "retry_chexbox_tag"; + public static final boolean IS_ENABLED = false; + private static int MAX_LOAD_IMAGES = 5; + private int lastLoadImageIdx = 0; + + private SparseArray fileList = null; + CheckBox failed_upload_all_cb; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.failed_upload_files); + + 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(); + 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 + */ + private List getCheckboxList() { + List list = new ArrayList(); + for (int i = 0; i < listView.getChildCount(); i++) { + Log_OC.d(LOG_TAG, "ListView has Childs: " + listView.getChildCount()); + View childView = listView.getChildAt(i); + if (childView != null && childView instanceof ViewGroup) { + View checkboxView = getChildViews((ViewGroup) childView); + if (checkboxView != null && checkboxView instanceof CheckBox) { + Log_OC.d(LOG_TAG, "found Child: " + checkboxView.getId() + " " + checkboxView.getClass()); + list.add((CheckBox) checkboxView); + } + } + } + return list; + } + + /** + * recursive called method, used from getCheckboxList method + * + * @param View + * @return View + */ + private View getChildViews(ViewGroup view) { + if (view != null) { + for (int i = 0; i < view.getChildCount(); i++) { + View cb = view.getChildAt(i); + if (cb != null && cb instanceof ViewGroup) { + return getChildViews((ViewGroup) cb); + } else if (cb instanceof CheckBox) { + return cb; + } + } + } + return null; + } + + /** + * create a new OnCheckedChangeListener for the 'check all' checkbox * + * + * @return OnCheckedChangeListener to select all checkboxes at the list + */ + private OnCheckedChangeListener getCheckAllListener() { + return new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + List list = getCheckboxList(); + for (CheckBox checkbox : list) { + ((CheckBox) checkbox).setChecked(isChecked); + } + } + + }; + } + + /** + * Button click Listener for the retry button at the headline + * + * @return a Listener to perform a retry for all selected images + */ + private OnClickListener getRetryListner() { + return new OnClickListener() { + + @Override + public void onClick(View v) { + + try { + + List list = getCheckboxList(); + for (CheckBox checkbox : list) { + boolean to_retry = checkbox.isChecked(); + + Log_OC.d(LOG_TAG, "Checkbox for " + checkbox.getId() + " was checked: " + to_retry); + String img_path = fileList.get(checkbox.getId()); + if (to_retry) { + + final String msg = "Image-Path " + checkbox.getId() + " was checked: " + img_path; + Log_OC.d(LOG_TAG, msg); + startUpload(img_path); + } + + } + } finally { + // refresh the List + listView.removeAllViews(); + loadListView(true); + if (failed_upload_all_cb != null) { + failed_upload_all_cb.setChecked(false); + } + } + + } + }; + } + + /** + * Button click Listener for the delete button at the headline + * + * @return a Listener to perform a delete for all selected images + */ + private OnClickListener getDeleteListner() { + + return new OnClickListener() { + + @Override + public void onClick(View v) { + + final DbHandler dbh = new DbHandler(getApplicationContext()); + try { + List list = getCheckboxList(); + for (CheckBox checkbox : list) { + boolean to_be_delete = checkbox.isChecked(); + + Log_OC.d(LOG_TAG, "Checkbox for " + checkbox.getId() + " was checked: " + to_be_delete); + String img_path = fileList.get(checkbox.getId()); + Log_OC.d(LOG_TAG, "Image-Path " + checkbox.getId() + " was checked: " + img_path); + if (to_be_delete) { + boolean deleted = dbh.removeIUPendingFile(img_path); + Log_OC.d(LOG_TAG, "removing " + checkbox.getId() + " was : " + deleted); + + } + + } + } finally { + dbh.close(); + // refresh the List + listView.removeAllViews(); + loadListView(true); + if (failed_upload_all_cb != null) { + failed_upload_all_cb.setChecked(false); + } + } + + } + }; + } + + private LinearLayout getHorizontalLinearLayout(int id) { + LinearLayout linearLayout = new LinearLayout(getApplicationContext()); + linearLayout.setId(id); + linearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.MATCH_PARENT)); + linearLayout.setGravity(Gravity.RIGHT); + linearLayout.setOrientation(LinearLayout.HORIZONTAL); + return linearLayout; + } + + private LinearLayout getVerticalLinearLayout() { + LinearLayout linearLayout = new LinearLayout(getApplicationContext()); + linearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.MATCH_PARENT)); + linearLayout.setGravity(Gravity.TOP); + linearLayout.setOrientation(LinearLayout.VERTICAL); + return linearLayout; + } + + private View getFileButton(final String img_path, String message, int id) { + + TextView failureTextView = new TextView(this); + failureTextView.setText(getString(R.string.failed_upload_failure_text) + message); + failureTextView.setBackgroundResource(R.color.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.d(LOG_TAG, message); ++ 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; + } + } + + } diff --cc src/com/owncloud/android/ui/activity/UploadFilesActivity.java index a6d61d18,8a62219e..16e05f89 --- a/src/com/owncloud/android/ui/activity/UploadFilesActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadFilesActivity.java @@@ -26,7 -25,7 +25,6 @@@ import android.os.AsyncTask 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; diff --cc src/com/owncloud/android/ui/dialog/SslValidatorDialog.java index 2143e9a9,16e390da..a5766eee --- a/src/com/owncloud/android/ui/dialog/SslValidatorDialog.java +++ b/src/com/owncloud/android/ui/dialog/SslValidatorDialog.java @@@ -32,7 -31,7 +31,6 @@@ import javax.security.auth.x500.X500Pri 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; diff --cc src/com/owncloud/android/ui/fragment/ConfirmationDialogFragment.java index e527d31e,84a31b94..d528b341 --- a/src/com/owncloud/android/ui/fragment/ConfirmationDialogFragment.java +++ b/src/com/owncloud/android/ui/fragment/ConfirmationDialogFragment.java @@@ -23,9 -22,10 +22,9 @@@ import android.app.AlertDialog 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 { diff --cc src/com/owncloud/android/ui/fragment/FileDetailFragment.java index 4be1fbbf,32d8f9ad..ec20f869 --- a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java @@@ -1,1043 -1,1060 +1,1006 @@@ - /* 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 as published by - * the Free Software Foundation, either version 2 of the License, or - * (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 details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - package com.owncloud.android.ui.fragment; - - import java.io.File; - - import org.apache.commons.httpclient.Credentials; - - 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.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.DisplayUtils; - import com.owncloud.android.authentication.AccountAuthenticator; - import com.owncloud.android.datamodel.FileDataStorageManager; - import com.owncloud.android.datamodel.OCFile; - import com.owncloud.android.files.services.FileDownloader; - import com.owncloud.android.files.services.FileObserverService; - import com.owncloud.android.files.services.FileUploader; - import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; - import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; - import com.owncloud.android.network.BearerCredentials; - 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.activity.TransferServiceGetter; - import com.owncloud.android.ui.dialog.EditNameDialog; - import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener; - - import com.owncloud.android.R; - import eu.alefzero.webdav.WebdavUtils; - - /** - * This Fragment is used to display the details about a file. - * - * @author Bartek Przybylski - * - */ - public class FileDetailFragment extends SherlockFragment implements - OnClickListener, ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener, EditNameDialogListener { - - public static final String EXTRA_FILE = "FILE"; - public static final String EXTRA_ACCOUNT = "ACCOUNT"; - - private FileDetailFragment.ContainerActivity mContainerActivity; - - private int mLayout; - private View mView; - private OCFile mFile; - private Account mAccount; - private FileDataStorageManager mStorageManager; - private ImageView mPreview; - - private DownloadFinishReceiver mDownloadFinishReceiver; - private UploadFinishReceiver mUploadFinishReceiver; - - private Handler mHandler; - private RemoteOperation mLastRemoteOperation; - private DialogFragment mCurrentDialog; - - 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; - } - - - /** - * 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; - } - - - @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); - mPreview = (ImageView)mView.findViewById(R.id.fdPreview); - } - - updateFileDetails(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) { - Log.i(getClass().toString(), "onSaveInstanceState() start"); - super.onSaveInstanceState(outState); - outState.putParcelable(FileDetailFragment.EXTRA_FILE, mFile); - outState.putParcelable(FileDetailFragment.EXTRA_ACCOUNT, mAccount); - Log.i(getClass().toString(), "onSaveInstanceState() end"); - } - - - @Override - public void onResume() { - super.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); - - mPreview = (ImageView)mView.findViewById(R.id.fdPreview); - } - - @Override - public void onPause() { - super.onPause(); - - getActivity().unregisterReceiver(mDownloadFinishReceiver); - mDownloadFinishReceiver = null; - - getActivity().unregisterReceiver(mUploadFinishReceiver); - mUploadFinishReceiver = null; - - if (mPreview != null) { - mPreview = null; - } - } - + /* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + package com.owncloud.android.ui.fragment; + + import java.io.File; + import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; - -import 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 + 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()); + mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); + + // update ui + boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; + getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); + setButtonsForTransferring(); // disable button immediately, although the synchronization does not result in a file transference + + } + break; + } - 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); - Log.e(TAG, "starting observer service"); - getActivity().startService(intent); - - if (mFile.keepInSync()) { - onClick(getView().findViewById(R.id.fdDownloadBtn)); // force an immediate synchronization - } - break; - } - case R.id.fdRenameBtn: { - EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mFile.getFileName(), 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); - mCurrentDialog = confDialog; - mCurrentDialog.show(getFragmentManager(), FTAG_CONFIRMATION); - break; - } - case R.id.fdOpenBtn: { - 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()); - 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); - - } catch (ActivityNotFoundException e) { - Log.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); - - } finally { - if (toastIt) { - Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show(); - } - } - - } - break; - } - default: - Log.e(TAG, "Incorrect view clicked!"); - } - - /* else if (v.getId() == R.id.fdShareBtn) { - Thread t = new Thread(new ShareRunnable(mFile.getRemotePath())); - t.start(); - }*/ - } - - - @Override - public void onConfirmation(String callerTag) { - if (callerTag.equals(FTAG_CONFIRMATION)) { - if (mStorageManager.getFileById(mFile.getFileId()) != null) { - mLastRemoteOperation = new RemoveFileOperation( mFile, - 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); - } - } - mCurrentDialog.dismiss(); - mCurrentDialog = null; - } - - @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); - } - mCurrentDialog.dismiss(); - mCurrentDialog = null; - } - - @Override - public void onCancel(String callerTag) { - Log.d(TAG, "REMOVAL CANCELED"); - mCurrentDialog.dismiss(); - mCurrentDialog = null; - } - - - /** - * 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); - } - - - /** - * Can be used to get the file that is currently being displayed. - * @return The file on the screen. - */ - public OCFile getDisplayedFile(){ - 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); - } - - - /** - * Updates the view with all relevant details about that file. - * - * TODO Remove parameter when the transferring state of files is kept in database. - * - * @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. - * - */ - public void updateFileDetails(boolean transferring) { - - if (mFile != null && mAccount != null && mLayout == R.layout.file_details_fragment) { - - // 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()) { - // Update preview - if (mFile.getMimetype().startsWith("image/")) { - BitmapLoader bl = new BitmapLoader(); - bl.execute(new String[]{mFile.getStoragePath()}); - } - - setButtonsForDown(); - - } else { - // TODO load default preview image; when the local file is removed, the preview remains there - setButtonsForRemote(); - } - } - getView().invalidate(); - } - - - /** - * 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); - } - } - - /** - * 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); - } - } - - /** - * 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); - } - } - - - /** - * 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; - } - - - /** - * Interface to implement by any Activity that includes some instance of FileDetailFragment - * - * @author David A. Velasco - */ - public interface ContainerActivity extends TransferServiceGetter { - - /** - * Callback method invoked when the detail fragment wants to notice its container - * activity about a relevant state the file shown by the fragment. - * - * Added to notify to FileDisplayActivity about the need of refresh the files list. - * - * Currently called when: - * - a download is started; - * - a rename is completed; - * - a deletion is completed; - * - the 'inSync' flag is changed; - */ - public void onFileStateChanged(); - - } - - - /** - * Once the file download has finished -> update view - * @author Bartek Przybylski - */ - private class DownloadFinishReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME); - - if (!isEmpty() && accountName.equals(mAccount.name)) { - boolean downloadWasFine = intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false); - String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); - if (mFile.getRemotePath().equals(downloadedRemotePath)) { - if (downloadWasFine) { - mFile = mStorageManager.getFileByPath(downloadedRemotePath); - } - updateFileDetails(false); // it updates the buttons; must be called although !downloadWasFine - } - } - } - } - - - /** - * 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); // 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 formparams = new ArrayList(); - 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); - mLastRemoteOperation = new RenameFileOperation( mFile, - mAccount, - newFilename, - new FileDataStorageManager(mAccount, getActivity().getContentResolver())); - mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); - boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; - getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); - } - } - - - class BitmapLoader extends AsyncTask { - @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 { - - BitmapFactory.Options options = new Options(); - options.inScaled = true; - options.inPurgeable = true; - options.inJustDecodeBounds = 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; - } - - result = BitmapFactory.decodeFile(storagePath, options); - options.inJustDecodeBounds = false; - - int width = options.outWidth; - int height = options.outHeight; - int scale = 1; - if (width >= 2048 || height >= 2048) { - scale = (int) Math.ceil((Math.ceil(Math.max(height, width) / 2048.))); - options.inSampleSize = scale; - } - Display display = getActivity().getWindowManager().getDefaultDisplay(); - Point size = new Point(); - int screenwidth; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR2) { - display.getSize(size); - screenwidth = size.x; - } else { - screenwidth = display.getWidth(); - } - - Log.e("ASD", "W " + width + " SW " + screenwidth); - - if (width > screenwidth) { - scale = (int) Math.ceil((float)width / screenwidth); - options.inSampleSize = scale; - } - - result = BitmapFactory.decodeFile(storagePath, options); - - Log.e("ASD", "W " + options.outWidth + " SW " + options.outHeight); - - } catch (OutOfMemoryError e) { - result = null; - Log.e(TAG, "Out of memory occured for file with size " + storagePath); - - } catch (NoSuchFieldError e) { - result = null; - Log.e(TAG, "Error from access to unexisting field despite protection " + storagePath); - - } catch (Throwable t) { - result = null; - Log.e(TAG, "Unexpected error while creating image preview " + storagePath, t); - } - return result; - } - @Override - protected void onPostExecute(Bitmap result) { - if (result != null && mPreview != null) { - mPreview.setImageBitmap(result); - } - } - - } - - /** - * {@inheritDoc} - */ - @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); - } - } - - - 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()) { - 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(); - } - } - } - } - - } + 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(); - }*/ - } - - ++ } ++ + /** + * 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 formparams = new ArrayList(); - 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 mProgressBar = null; + + ProgressListener(ProgressBar progressBar) { + mProgressBar = new WeakReference(progressBar); + } + + @Override + public void onTransferProgress(long progressRate) { + // old method, nothing here + }; + + @Override + public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filename) { + int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer)); + if (percent != mLastPercent) { + ProgressBar pb = mProgressBar.get(); + if (pb != null) { + pb.setProgress(percent); + pb.postInvalidate(); + } + } + mLastPercent = percent; + } + + }; + -} ++ /* ++ // 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 formparams = new ArrayList(); ++ 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) { ++ ++ } ++ } ++ } ++ */ ++ ++} diff --cc src/com/owncloud/android/ui/fragment/LocalFileListFragment.java index 51f78b70,fecf26cd..4e6affa6 --- a/src/com/owncloud/android/ui/fragment/LocalFileListFragment.java +++ b/src/com/owncloud/android/ui/fragment/LocalFileListFragment.java @@@ -26,7 -25,7 +25,6 @@@ import com.owncloud.android.ui.adapter. 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; diff --cc src/com/owncloud/android/ui/fragment/OCFileListFragment.java index 0e5e879a,dac32c85..f8728032 --- a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java @@@ -50,8 -52,7 +50,6 @@@ import android.content.Intent 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; @@@ -312,10 -314,11 +311,10 @@@ public class OCFileListFragment extend } 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; } diff --cc src/com/owncloud/android/ui/preview/FileDownloadFragment.java index 00000000,b11d341c..773350af mode 000000,100644..100644 --- a/src/com/owncloud/android/ui/preview/FileDownloadFragment.java +++ b/src/com/owncloud/android/ui/preview/FileDownloadFragment.java @@@ -1,0 -1,396 +1,396 @@@ + /* 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 as published by - * the Free Software Foundation, either version 3 of the License. ++ * it under the terms of the GNU General Public License version 2, ++ * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + package com.owncloud.android.ui.preview; + + import java.lang.ref.WeakReference; + + import android.accounts.Account; + import android.app.Activity; + import android.os.Bundle; + import android.support.v4.app.FragmentStatePagerAdapter; -import android.util.Log; + 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.e(TAG, "Incorrect view clicked!"); ++ 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 mProgressBar = null; + + ProgressListener(ProgressBar progressBar) { + mProgressBar = new WeakReference(progressBar); + } + + @Override + public void onTransferProgress(long progressRate) { + // old method, nothing here + }; + + @Override + public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filename) { + int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer)); + if (percent != mLastPercent) { + ProgressBar pb = mProgressBar.get(); + if (pb != null) { + pb.setProgress(percent); + pb.postInvalidate(); + } + } + mLastPercent = percent; + } + + } + + + public void setError(boolean error) { + mError = error; + }; + + + + } diff --cc src/com/owncloud/android/ui/preview/PreviewImageActivity.java index 00000000,be06c590..9b3e7d88 mode 000000,100644..100644 --- a/src/com/owncloud/android/ui/preview/PreviewImageActivity.java +++ b/src/com/owncloud/android/ui/preview/PreviewImageActivity.java @@@ -1,0 -1,448 +1,445 @@@ + /* ownCloud Android client application + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + package com.owncloud.android.ui.preview; + -import org.apache.commons.httpclient.methods.PostMethod; - + 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.support.v4.app.Fragment; + import android.support.v4.view.ViewPager; -import android.util.Log; + 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, "Simulating reselection of current page after connection of download binder"); ++ 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, "Upload service connected"); ++ 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, "Download 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, "Upload service suddenly disconnected"); ++ 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, "requestForDownload called without binder to download service"); ++ 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.d(TAG, "Download finished, but the fragment is offscreen"); ++ 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; + } + + + } diff --cc src/com/owncloud/android/ui/preview/PreviewImageFragment.java index 00000000,2f51a631..8df06bfe mode 000000,100644..100644 --- a/src/com/owncloud/android/ui/preview/PreviewImageFragment.java +++ b/src/com/owncloud/android/ui/preview/PreviewImageFragment.java @@@ -1,0 -1,685 +1,682 @@@ + /* ownCloud Android client application + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + package com.owncloud.android.ui.preview; + + import java.io.File; + import java.lang.ref.WeakReference; + import java.util.ArrayList; + import java.util.List; + + + import android.accounts.Account; + import android.annotation.SuppressLint; + import android.app.Activity; + import android.content.ActivityNotFoundException; + import android.content.Intent; + import android.graphics.Bitmap; + import android.graphics.BitmapFactory; + import android.graphics.BitmapFactory.Options; + import android.graphics.Point; + import android.net.Uri; + import android.os.AsyncTask; + import android.os.Bundle; + import android.os.Handler; + import android.support.v4.app.FragmentStatePagerAdapter; -import android.util.Log; + 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 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.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; -import eu.alefzero.webdav.WebdavClient; + 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 toHide = new ArrayList(); + + MenuItem item = null; + toHide.add(R.id.action_cancel_download); + toHide.add(R.id.action_cancel_upload); + toHide.add(R.id.action_download_file); + toHide.add(R.id.action_rename_file); // by now + + for (int i : toHide) { + item = menu.findItem(i); + if (item != null) { + item.setVisible(false); + item.setEnabled(false); + } + } + + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_open_file_with: { + openFile(); + return true; + } + case R.id.action_remove_file: { + removeFile(); + return true; + } + case R.id.action_see_details: { + seeDetails(); + return true; + } + + default: + return false; + } + } + + + private void seeDetails() { + ((FileFragment.ContainerActivity)getActivity()).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, "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(); + } + } + + } + 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); - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); - mLastRemoteOperation.execute(wc, this, mHandler); ++ 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 { + + /** + * Weak reference to the target {@link ImageView} where the bitmap will be loaded into. + * + * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes. + */ + private final WeakReference mImageViewRef; + + /** + * Weak reference to the target {@link TextView} where error messages will be written. + * + * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes. + */ + private final WeakReference mMessageViewRef; + + + /** + * Weak reference to the target {@link Progressbar} shown while the load is in progress. + * + * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes. + */ + private final WeakReference mProgressWheelRef; + + + /** + * Error message to show when a load fails + */ + private int mErrorMessageId; + + + /** + * Constructor. + * + * @param imageView Target {@link ImageView} where the bitmap will be loaded into. + */ + public BitmapLoader(ImageView imageView, TextView messageView, ProgressBar progressWheel) { + mImageViewRef = new WeakReference(imageView); + mMessageViewRef = new WeakReference(messageView); + mProgressWheelRef = new WeakReference(progressWheel); + } + + + @SuppressWarnings("deprecation") + @SuppressLint({ "NewApi", "NewApi", "NewApi" }) // to avoid Lint errors since Android SDK r20 + @Override + protected Bitmap doInBackground(String... params) { + Bitmap result = null; + if (params.length != 1) return result; + String storagePath = params[0]; + try { + // set desired options that will affect the size of the bitmap + BitmapFactory.Options options = new Options(); + options.inScaled = true; + options.inPurgeable = true; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + options.inPreferQualityOverSpeed = false; + } + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { + options.inMutable = false; + } + // make a false load of the bitmap - just to be able to read outWidth, outHeight and outMimeType + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(storagePath, options); + + int width = options.outWidth; + int height = options.outHeight; + int scale = 1; + + Display display = getActivity().getWindowManager().getDefaultDisplay(); + Point size = new Point(); + int screenWidth; + int screenHeight; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR2) { + display.getSize(size); + screenWidth = size.x; + screenHeight = size.y; + } else { + screenWidth = display.getWidth(); + screenHeight = display.getHeight(); + } + + if (width > screenWidth) { + // second try to scale down the image , this time depending upon the screen size + scale = (int) Math.floor((float)width / screenWidth); + } + if (height > screenHeight) { + scale = Math.max(scale, (int) Math.floor((float)height / screenHeight)); + } + options.inSampleSize = scale; + + // really load the bitmap + options.inJustDecodeBounds = false; // the next decodeFile call will be real + result = BitmapFactory.decodeFile(storagePath, options); - //Log.d(TAG, "Image loaded - width: " + options.outWidth + ", loaded height: " + options.outHeight); ++ //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, "File could not be loaded as a bitmap: " + storagePath); ++ 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, "Out of memory occured for 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, "Error from access to unexisting field despite protection; file " + storagePath, e); ++ 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.e(TAG, "Unexpected error loading " + mFile.getStoragePath(), t); ++ 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(); + } + + + } diff --cc src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java index 00000000,b5945bd0..a2536242 mode 000000,100644..100644 --- a/src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java +++ b/src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java @@@ -1,0 -1,352 +1,344 @@@ + /* ownCloud Android client application + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + package com.owncloud.android.ui.preview; + -import java.util.ArrayList; + 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.os.Bundle; -import android.os.Parcelable; + import android.support.v4.app.Fragment; + import android.support.v4.app.FragmentManager; + import android.support.v4.app.FragmentStatePagerAdapter; -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.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 mImageFiles; + private Account mAccount; + private Set mObsoleteFragments; + private Set mObsoletePositions; + private Set mDownloadErrors; + private DataStorageManager mStorageManager; + + private Map mCachedFragments; + + /* + private final FragmentManager mFragmentManager; + private FragmentTransaction mCurTransaction = null; + private ArrayList mSavedState = new ArrayList(); + private ArrayList mFragments = new ArrayList(); + 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(); + mObsoletePositions = new HashSet(); + mDownloadErrors = new HashSet(); + //mFragmentManager = fragmentManager; + mCachedFragments = new HashMap(); + } + + + /** + * Returns the image files handled by the adapter. + * + * @return A vector with the image files handled by the adapter. + */ + protected OCFile getFileAt(int position) { + return mImageFiles.get(position); + } + + + public Fragment getItem(int i) { + OCFile file = mImageFiles.get(i); + Fragment fragment = null; + if (file.isDown()) { + fragment = new PreviewImageFragment(file, mAccount, mObsoletePositions.contains(Integer.valueOf(i))); + + } else if (mDownloadErrors.contains(Integer.valueOf(i))) { + fragment = new FileDownloadFragment(file, mAccount, true); + ((FileDownloadFragment)fragment).setError(true); + mDownloadErrors.remove(Integer.valueOf(i)); + + } else { + fragment = new FileDownloadFragment(file, mAccount, mObsoletePositions.contains(Integer.valueOf(i))); + } + mObsoletePositions.remove(Integer.valueOf(i)); + return fragment; + } + + public int getFilePosition(OCFile file) { + return mImageFiles.indexOf(file); + } + + @Override + public int getCount() { + return mImageFiles.size(); + } + + @Override + public CharSequence getPageTitle(int position) { + return mImageFiles.get(position).getFileName(); + } + + + public void updateFile(int position, OCFile file) { + FileFragment fragmentToUpdate = mCachedFragments.get(Integer.valueOf(position)); + if (fragmentToUpdate != null) { + mObsoleteFragments.add(fragmentToUpdate); + } + mObsoletePositions.add(Integer.valueOf(position)); + mImageFiles.set(position, file); + } + + + public void updateWithDownloadError(int position) { + FileFragment fragmentToUpdate = mCachedFragments.get(Integer.valueOf(position)); + if (fragmentToUpdate != null) { + mObsoleteFragments.add(fragmentToUpdate); + } + mDownloadErrors.add(Integer.valueOf(position)); + } + + public void clearErrorAt(int position) { + FileFragment fragmentToUpdate = mCachedFragments.get(Integer.valueOf(position)); + if (fragmentToUpdate != null) { + mObsoleteFragments.add(fragmentToUpdate); + } + mDownloadErrors.remove(Integer.valueOf(position)); + } + + + @Override + public int getItemPosition(Object object) { + if (mObsoleteFragments.contains(object)) { + mObsoleteFragments.remove(object); + return POSITION_NONE; + } + return super.getItemPosition(object); + } + + + @Override + public Object instantiateItem(ViewGroup container, int position) { + Object fragment = super.instantiateItem(container, position); + mCachedFragments.put(Integer.valueOf(position), (FileFragment)fragment); + return fragment; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + mCachedFragments.remove(Integer.valueOf(position)); + super.destroyItem(container, position, object); + } + + + public boolean pendingErrorAt(int position) { + return mDownloadErrors.contains(Integer.valueOf(position)); + } + + + + /* -* + * Called when a change in the shown pages is going to start being made. + * + * @param container The containing View which is displaying this adapter's page views. + *- / + @Override + public void startUpdate(ViewGroup container) { + Log.e(TAG, "** startUpdate"); + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + Log.e(TAG, "** instantiateItem " + position); + + if (mFragments.size() > position) { + Fragment fragment = mFragments.get(position); + if (fragment != null) { + Log.e(TAG, "** \t returning cached item"); + return fragment; + } + } + + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + + Fragment fragment = getItem(position); + if (mSavedState.size() > position) { + Fragment.SavedState savedState = mSavedState.get(position); + if (savedState != null) { + // TODO WATCH OUT: + // * The Fragment must currently be attached to the FragmentManager. + // * A new Fragment created using this saved state must be the same class type as the Fragment it was created from. + // * The saved state can not contain dependencies on other fragments -- that is it can't use putFragment(Bundle, String, Fragment) + // to store a fragment reference + fragment.setInitialSavedState(savedState); + } + } + while (mFragments.size() <= position) { + mFragments.add(null); + } + fragment.setMenuVisibility(false); + mFragments.set(position, fragment); + //Log.e(TAG, "** \t adding fragment at position " + position + ", containerId " + container.getId()); + mCurTransaction.add(container.getId(), fragment); + + return fragment; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + Log.e(TAG, "** destroyItem " + position); + Fragment fragment = (Fragment)object; + + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + Log.e(TAG, "** \t removing fragment at position " + position); + while (mSavedState.size() <= position) { + mSavedState.add(null); + } + mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment)); + mFragments.set(position, null); + + mCurTransaction.remove(fragment); + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment)object; + if (fragment != mCurrentPrimaryItem) { + if (mCurrentPrimaryItem != null) { + mCurrentPrimaryItem.setMenuVisibility(false); + } + if (fragment != null) { + fragment.setMenuVisibility(true); + } + mCurrentPrimaryItem = fragment; + } + } + + @Override + public void finishUpdate(ViewGroup container) { + Log.e(TAG, "** finishUpdate (start)"); + if (mCurTransaction != null) { + mCurTransaction.commitAllowingStateLoss(); + mCurTransaction = null; + mFragmentManager.executePendingTransactions(); + } + Log.e(TAG, "** finishUpdate (end)"); + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return ((Fragment)object).getView() == view; + } + + @Override + public Parcelable saveState() { + Bundle state = null; + if (mSavedState.size() > 0) { + state = new Bundle(); + Fragment.SavedState[] savedStates = new Fragment.SavedState[mSavedState.size()]; + mSavedState.toArray(savedStates); + state.putParcelableArray("states", savedStates); + } + for (int i=0; i keys = bundle.keySet(); + for (String key: keys) { + if (key.startsWith("f")) { + int index = Integer.parseInt(key.substring(1)); + Fragment f = mFragmentManager.getFragment(bundle, key); + if (f != null) { + while (mFragments.size() <= index) { + mFragments.add(null); + } + f.setMenuVisibility(false); + mFragments.set(index, f); + } else { + Log.w(TAG, "Bad fragment at key " + key); + } + } + } + } + } + */ + } diff --cc src/com/owncloud/android/ui/preview/PreviewMediaFragment.java index 00000000,6fbf7d66..c26d8c74 mode 000000,100644..100644 --- a/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java +++ b/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java @@@ -1,0 -1,755 +1,752 @@@ + /* ownCloud Android client application + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + package com.owncloud.android.ui.preview; + + import java.io.File; + import java.util.ArrayList; + import java.util.List; + + import android.accounts.Account; + import android.app.Activity; + import android.app.AlertDialog; + import android.content.ActivityNotFoundException; + import android.content.ComponentName; + import android.content.Context; + import android.content.DialogInterface; + import android.content.Intent; + import android.content.ServiceConnection; + import android.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 android.util.Log; + 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 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.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; -import eu.alefzero.webdav.WebdavClient; + 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 toHide = new ArrayList(); + + MenuItem item = null; + toHide.add(R.id.action_cancel_download); + toHide.add(R.id.action_cancel_upload); + toHide.add(R.id.action_download_file); + toHide.add(R.id.action_rename_file); // by now + + for (int i : toHide) { + item = menu.findItem(i); + if (item != null) { + item.setVisible(false); + item.setEnabled(false); + } + } + + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_open_file_with: { + openFile(); + return true; + } + case R.id.action_remove_file: { + removeFile(); + return true; + } + case R.id.action_see_details: { + seeDetails(); + return true; + } + + default: + return false; + } + } + + + private void seeDetails() { + 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, "onPrepared"); ++ 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.e(TAG, "completed"); ++ 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, "Unbinding from MediaService ..."); ++ 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, "starting playback of " + mFile.getStoragePath()); ++ 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, "Binding to MediaService..."); ++ 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, "Media service connected"); ++ 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.d(TAG, "Successfully bound to MediaService, MediaController ready"); ++ Log_OC.d(TAG, "Successfully bound to MediaService, MediaController ready"); + + } else { - Log.e(TAG, "Unexpected response from MediaService while binding"); ++ 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, "Media service suddenly disconnected"); ++ 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, "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(); + } + } + + } + 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); - 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); + } + } + + + /** + * 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(); + } + } + + } diff --cc src/com/owncloud/android/ui/preview/PreviewVideoActivity.java index 00000000,c335bf4a..3cbff7d8 mode 000000,100644..100644 --- a/src/com/owncloud/android/ui/preview/PreviewVideoActivity.java +++ b/src/com/owncloud/android/ui/preview/PreviewVideoActivity.java @@@ -1,0 -1,282 +1,282 @@@ + /* ownCloud Android client application + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + package com.owncloud.android.ui.preview; + + import android.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; -import android.util.Log; + 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\tonCreate"); ++ 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, "ACTIVITY\t\tonSaveInstanceState"); ++ 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\tonBackPressed"); ++ 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\tonResume"); ++ Log_OC.e(TAG, "ACTIVTIY\t\tonResume"); + } + + + @Override + public void onStart() { + super.onStart(); - Log.e(TAG, "ACTIVTIY\t\tonStart"); ++ Log_OC.e(TAG, "ACTIVTIY\t\tonStart"); + } + + @Override + public void onDestroy() { + super.onDestroy(); - Log.e(TAG, "ACTIVITY\t\tonDestroy"); ++ Log_OC.e(TAG, "ACTIVITY\t\tonDestroy"); + } + + @Override + public void onStop() { + super.onStop(); - Log.e(TAG, "ACTIVTIY\t\tonStop"); ++ Log_OC.e(TAG, "ACTIVTIY\t\tonStop"); + } + + + @Override + public void onPause() { + super.onPause(); - Log.e(TAG, "ACTIVTIY\t\tonPause"); ++ 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, "ACTIVITY\t\tonPrepare"); ++ 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.e(TAG, "Error in video playback, what = " + what + ", extra = " + 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; + } + + + } diff --cc src/com/owncloud/android/utils/FileStorageUtils.java index 5406cb5a,1c7acaf9..dda7b5a5 --- a/src/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@@ -23,10 -24,11 +24,10 @@@ import android.content.Context 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. * diff --cc src/eu/alefzero/webdav/WebdavClient.java index 7577152d,9830f668..0b3cf818 --- a/src/eu/alefzero/webdav/WebdavClient.java +++ b/src/eu/alefzero/webdav/WebdavClient.java @@@ -1,346 -1,341 +1,344 @@@ - /* 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 as published by - * the Free Software Foundation, either version 2 of the License, or - * (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 details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - 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 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.d(TAG, "Creating WebdavClient"); - getParams().setParameter(HttpMethodParams.USER_AGENT, USER_AGENT); - getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1); - } - - public void setBearerCredentials(String accessToken) { - AuthPolicy.registerAuthScheme(BearerAuthScheme.AUTH_POLICY, BearerAuthScheme.class); - - List authPrefs = new ArrayList(1); - authPrefs.add(BearerAuthScheme.AUTH_POLICY); - getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs); - - mCredentials = new BearerCredentials(accessToken); - getState().setCredentials(AuthScope.ANY, mCredentials); - } - - public void setBasicCredentials(String username, String password) { - List authPrefs = new ArrayList(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.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.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.addOnDatatransferProgressListener(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.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; - } - - - /** - * 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.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.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.e(TAG, "HTTP violation while " + doing, e); - - } else if (e instanceof IOException) { - Log.e(TAG, "Unrecovered transport exception while " + doing, e); - - } else { - Log.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; - } - - + /* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ ++ + package 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 authPrefs = new ArrayList(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 authPrefs = new ArrayList(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 + public int executeMethod(HostConfiguration hostconfig, final HttpMethod method, final HttpState state) throws IOException, HttpException { + if (mCredentials instanceof BearerCredentials) { + method.getHostAuthState().setAuthScheme(AuthPolicy.getAuthScheme(BearerAuthScheme.AUTH_POLICY)); + method.getHostAuthState().setAuthAttempted(true); + } + return super.executeMethod(hostconfig, method, state); + } + + + public final Credentials getCredentials() { + return mCredentials; + } + - } + } diff --cc src/eu/alefzero/webdav/WebdavEntry.java index f76ee697,450ca6bc..46923c66 --- a/src/eu/alefzero/webdav/WebdavEntry.java +++ b/src/eu/alefzero/webdav/WebdavEntry.java @@@ -24,8 -23,10 +23,9 @@@ import org.apache.jackrabbit.webdav.pro 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;