From: David A. Velasco Date: Tue, 12 Nov 2013 14:22:36 +0000 (+0100) Subject: Merge branch 'develop' into refactor_remote_operation_to_create_folder X-Git-Tag: oc-android-1.5.5~123^2~14 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/89b37b750836b661e33875cad8ac00c3027e958c?hp=-c Merge branch 'develop' into refactor_remote_operation_to_create_folder --- 89b37b750836b661e33875cad8ac00c3027e958c diff --combined oc_framework/src/com/owncloud/android/oc_framework/network/AdvancedSslSocketFactory.java index badb2439,00000000..e24fa364 mode 100644,000000..100644 --- a/oc_framework/src/com/owncloud/android/oc_framework/network/AdvancedSslSocketFactory.java +++ b/oc_framework/src/com/owncloud/android/oc_framework/network/AdvancedSslSocketFactory.java @@@ -1,241 -1,0 +1,242 @@@ +/* 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.oc_framework.network; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.UnknownHostException; +import java.security.cert.X509Certificate; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +import org.apache.commons.httpclient.ConnectTimeoutException; +import org.apache.commons.httpclient.params.HttpConnectionParams; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; +import org.apache.http.conn.ssl.X509HostnameVerifier; + +import android.util.Log; + + ++ +/** + * AdvancedSSLProtocolSocketFactory allows to create SSL {@link Socket}s with + * a custom SSLContext and an optional Hostname Verifier. + * + * @author David A. Velasco + */ + +public class AdvancedSslSocketFactory implements ProtocolSocketFactory { + + private static final String TAG = AdvancedSslSocketFactory.class.getSimpleName(); + + private SSLContext mSslContext = null; + private AdvancedX509TrustManager mTrustManager = null; + private X509HostnameVerifier mHostnameVerifier = null; + + public SSLContext getSslContext() { + return mSslContext; + } + + /** + * Constructor for AdvancedSSLProtocolSocketFactory. + */ + public AdvancedSslSocketFactory(SSLContext sslContext, AdvancedX509TrustManager trustManager, X509HostnameVerifier hostnameVerifier) { + if (sslContext == null) + throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null SSLContext"); + if (trustManager == null) + throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null Trust Manager"); + mSslContext = sslContext; + mTrustManager = trustManager; + mHostnameVerifier = hostnameVerifier; + } + + /** + * @see ProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int) + */ + public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException { + Socket socket = mSslContext.getSocketFactory().createSocket(host, port, clientHost, clientPort); + verifyPeerIdentity(host, port, socket); + return socket; + } + + + /** + * Attempts to get a new socket connection to the given host within the + * given time limit. + * + * @param host the host name/IP + * @param port the port on the host + * @param clientHost the local host name/IP to bind the socket to + * @param clientPort the port on the local machine + * @param params {@link HttpConnectionParams Http connection parameters} + * + * @return Socket a new socket + * + * @throws IOException if an I/O error occurs while creating the socket + * @throws UnknownHostException if the IP address of the host cannot be + * determined + */ + public Socket createSocket(final String host, final int port, + final InetAddress localAddress, final int localPort, + final HttpConnectionParams params) throws IOException, + UnknownHostException, ConnectTimeoutException { + Log.d(TAG, "Creating SSL Socket with remote " + host + ":" + port + ", local " + localAddress + ":" + localPort + ", params: " + params); + if (params == null) { + throw new IllegalArgumentException("Parameters may not be null"); + } + int timeout = params.getConnectionTimeout(); + SocketFactory socketfactory = mSslContext.getSocketFactory(); + Log.d(TAG, " ... with connection timeout " + timeout + " and socket timeout " + params.getSoTimeout()); + Socket socket = socketfactory.createSocket(); + SocketAddress localaddr = new InetSocketAddress(localAddress, localPort); + SocketAddress remoteaddr = new InetSocketAddress(host, port); + socket.setSoTimeout(params.getSoTimeout()); + socket.bind(localaddr); + socket.connect(remoteaddr, timeout); + verifyPeerIdentity(host, port, socket); + return socket; + } + + /** + * @see ProtocolSocketFactory#createSocket(java.lang.String,int) + */ + public Socket createSocket(String host, int port) throws IOException, + UnknownHostException { + Log.d(TAG, "Creating SSL Socket with remote " + host + ":" + port); + Socket socket = mSslContext.getSocketFactory().createSocket(host, port); + verifyPeerIdentity(host, port, socket); + return socket; + } + + public boolean equals(Object obj) { + return ((obj != null) && obj.getClass().equals( + AdvancedSslSocketFactory.class)); + } + + public int hashCode() { + return AdvancedSslSocketFactory.class.hashCode(); + } + + + public X509HostnameVerifier getHostNameVerifier() { + return mHostnameVerifier; + } + + + public void setHostNameVerifier(X509HostnameVerifier hostnameVerifier) { + mHostnameVerifier = hostnameVerifier; + } + + /** + * Verifies the identity of the server. + * + * The server certificate is verified first. + * + * Then, the host name is compared with the content of the server certificate using the current host name verifier, if any. + * @param socket + */ + private void verifyPeerIdentity(String host, int port, Socket socket) throws IOException { + try { + CertificateCombinedException failInHandshake = null; + /// 1. VERIFY THE SERVER CERTIFICATE through the registered TrustManager (that should be an instance of AdvancedX509TrustManager) + try { + SSLSocket sock = (SSLSocket) socket; // a new SSLSession instance is created as a "side effect" + sock.startHandshake(); + + } catch (RuntimeException e) { + + if (e instanceof CertificateCombinedException) { + failInHandshake = (CertificateCombinedException) e; + } else { + Throwable cause = e.getCause(); + Throwable previousCause = null; + while (cause != null && cause != previousCause && !(cause instanceof CertificateCombinedException)) { + previousCause = cause; + cause = cause.getCause(); + } + if (cause != null && cause instanceof CertificateCombinedException) { + failInHandshake = (CertificateCombinedException)cause; + } + } + if (failInHandshake == null) { + throw e; + } + failInHandshake.setHostInUrl(host); + + } + + /// 2. VERIFY HOSTNAME + SSLSession newSession = null; + boolean verifiedHostname = true; + if (mHostnameVerifier != null) { + if (failInHandshake != null) { + /// 2.1 : a new SSLSession instance was NOT created in the handshake + X509Certificate serverCert = failInHandshake.getServerCertificate(); + try { + mHostnameVerifier.verify(host, serverCert); + } catch (SSLException e) { + verifiedHostname = false; + } + + } else { + /// 2.2 : a new SSLSession instance was created in the handshake + newSession = ((SSLSocket)socket).getSession(); + if (!mTrustManager.isKnownServer((X509Certificate)(newSession.getPeerCertificates()[0]))) { + verifiedHostname = mHostnameVerifier.verify(host, newSession); + } + } + } + + /// 3. Combine the exceptions to throw, if any + if (!verifiedHostname) { + SSLPeerUnverifiedException pue = new SSLPeerUnverifiedException("Names in the server certificate do not match to " + host + " in the URL"); + if (failInHandshake == null) { + failInHandshake = new CertificateCombinedException((X509Certificate) newSession.getPeerCertificates()[0]); + failInHandshake.setHostInUrl(host); + } + failInHandshake.setSslPeerUnverifiedException(pue); + pue.initCause(failInHandshake); + throw pue; + + } else if (failInHandshake != null) { + SSLHandshakeException hse = new SSLHandshakeException("Server certificate could not be verified"); + hse.initCause(failInHandshake); + throw hse; + } + + } catch (IOException io) { + try { + socket.close(); + } catch (Exception x) { + // NOTHING - irrelevant exception for the caller + } + throw io; + } + } + +} diff --combined oc_framework/src/com/owncloud/android/oc_framework/network/AdvancedX509TrustManager.java index 7d78f783,00000000..4bad3ef0 mode 100644,000000..100644 --- a/oc_framework/src/com/owncloud/android/oc_framework/network/AdvancedX509TrustManager.java +++ b/oc_framework/src/com/owncloud/android/oc_framework/network/AdvancedX509TrustManager.java @@@ -1,147 -1,0 +1,148 @@@ +/* 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.oc_framework.network; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertPathValidatorException; +import java.security.cert.CertStoreException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import android.util.Log; + + ++ +/** + * @author David A. Velasco + */ +public class AdvancedX509TrustManager implements X509TrustManager { + + private static final String TAG = AdvancedX509TrustManager.class.getSimpleName(); + + private X509TrustManager mStandardTrustManager = null; + private KeyStore mKnownServersKeyStore; + + /** + * Constructor for AdvancedX509TrustManager + * + * @param knownServersCertStore Local certificates store with server certificates explicitly trusted by the user. + * @throws CertStoreException When no default X509TrustManager instance was found in the system. + */ + public AdvancedX509TrustManager(KeyStore knownServersKeyStore) + throws NoSuchAlgorithmException, KeyStoreException, CertStoreException { + super(); + TrustManagerFactory factory = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + factory.init((KeyStore)null); + mStandardTrustManager = findX509TrustManager(factory); + + mKnownServersKeyStore = knownServersKeyStore; + } + + + /** + * Locates the first X509TrustManager provided by a given TrustManagerFactory + * @param factory TrustManagerFactory to inspect in the search for a X509TrustManager + * @return The first X509TrustManager found in factory. + * @throws CertStoreException When no X509TrustManager instance was found in factory + */ + private X509TrustManager findX509TrustManager(TrustManagerFactory factory) throws CertStoreException { + TrustManager tms[] = factory.getTrustManagers(); + for (int i = 0; i < tms.length; i++) { + if (tms[i] instanceof X509TrustManager) { + return (X509TrustManager) tms[i]; + } + } + return null; + } + + + /** + * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[], + * String authType) + */ + public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException { + mStandardTrustManager.checkClientTrusted(certificates, authType); + } + + + /** + * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], + * String authType) + */ + public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException { + if (!isKnownServer(certificates[0])) { + CertificateCombinedException result = new CertificateCombinedException(certificates[0]); + try { + certificates[0].checkValidity(); + } catch (CertificateExpiredException c) { + result.setCertificateExpiredException(c); + + } catch (CertificateNotYetValidException c) { + result.setCertificateNotYetException(c); + } + + try { + mStandardTrustManager.checkServerTrusted(certificates, authType); + } catch (CertificateException c) { + Throwable cause = c.getCause(); + Throwable previousCause = null; + while (cause != null && cause != previousCause && !(cause instanceof CertPathValidatorException)) { // getCause() is not funny + previousCause = cause; + cause = cause.getCause(); + } + if (cause != null && cause instanceof CertPathValidatorException) { + result.setCertPathValidatorException((CertPathValidatorException)cause); + } else { + result.setOtherCertificateException(c); + } + } + + if (result.isException()) + throw result; + + } + } + + + /** + * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() + */ + public X509Certificate[] getAcceptedIssuers() { + return mStandardTrustManager.getAcceptedIssuers(); + } + + + public boolean isKnownServer(X509Certificate cert) { + try { + return (mKnownServersKeyStore.getCertificateAlias(cert) != null); + } catch (KeyStoreException e) { + Log.d(TAG, "Fail while checking certificate in the known-servers store"); + return false; + } + } + +} diff --combined oc_framework/src/com/owncloud/android/oc_framework/network/BearerAuthScheme.java index 20e73663,00000000..37698d68 mode 100644,000000..100644 --- a/oc_framework/src/com/owncloud/android/oc_framework/network/BearerAuthScheme.java +++ b/oc_framework/src/com/owncloud/android/oc_framework/network/BearerAuthScheme.java @@@ -1,271 -1,0 +1,272 @@@ +package com.owncloud.android.oc_framework.network; +/* ownCloud Android client application + * Copyright (C) 2012 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + + +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; + + ++ +/** + * 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 */ + @SuppressWarnings("rawtypes") + 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)"); + + 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)"); + + 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)"); + + 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 --combined oc_framework/src/com/owncloud/android/oc_framework/network/webdav/ChunkFromFileChannelRequestEntity.java index 45877373,00000000..6a4200df mode 100644,000000..100644 --- a/oc_framework/src/com/owncloud/android/oc_framework/network/webdav/ChunkFromFileChannelRequestEntity.java +++ b/oc_framework/src/com/owncloud/android/oc_framework/network/webdav/ChunkFromFileChannelRequestEntity.java @@@ -1,146 -1,0 +1,145 @@@ +/* 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.oc_framework.network.webdav; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.apache.commons.httpclient.methods.RequestEntity; + +import com.owncloud.android.oc_framework.network.ProgressiveDataTransferer; + +import android.util.Log; + + - +/** + * A RequestEntity that represents a PIECE of a file. + * + * @author David A. Velasco + */ +public class ChunkFromFileChannelRequestEntity implements RequestEntity, ProgressiveDataTransferer { + + private static final String TAG = ChunkFromFileChannelRequestEntity.class.getSimpleName(); + + //private final File mFile; + private final FileChannel mChannel; + private final String mContentType; + private final long mChunkSize; + private final File mFile; + private long mOffset; + private long mTransferred; + Set mDataTransferListeners = new HashSet(); + private ByteBuffer mBuffer = ByteBuffer.allocate(4096); + + public ChunkFromFileChannelRequestEntity(final FileChannel channel, final String contentType, long chunkSize, final File file) { + super(); + if (channel == null) { + throw new IllegalArgumentException("File may not be null"); + } + if (chunkSize <= 0) { + throw new IllegalArgumentException("Chunk size must be greater than zero"); + } + mChannel = channel; + mContentType = contentType; + mChunkSize = chunkSize; + mFile = file; + mOffset = 0; + mTransferred = 0; + } + + public void setOffset(long offset) { + mOffset = offset; + } + + public long getContentLength() { + try { + return Math.min(mChunkSize, mChannel.size() - mChannel.position()); + } catch (IOException e) { + return mChunkSize; + } + } + + public String getContentType() { + return mContentType; + } + + public boolean isRepeatable() { + return true; + } + + @Override + public void addDatatransferProgressListener(OnDatatransferProgressListener listener) { + synchronized (mDataTransferListeners) { + mDataTransferListeners.add(listener); + } + } + + @Override + public void addDatatransferProgressListeners(Collection listeners) { + synchronized (mDataTransferListeners) { + mDataTransferListeners.addAll(listeners); + } + } + + @Override + public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) { + synchronized (mDataTransferListeners) { + mDataTransferListeners.remove(listener); + } + } + + + public void writeRequest(final OutputStream out) throws IOException { + int readCount = 0; + Iterator it = null; + + try { + mChannel.position(mOffset); + long size = mFile.length(); + if (size == 0) size = -1; + long maxCount = Math.min(mOffset + mChunkSize, mChannel.size()); + while (mChannel.position() < maxCount) { + readCount = mChannel.read(mBuffer); + out.write(mBuffer.array(), 0, readCount); + mBuffer.clear(); + if (mTransferred < maxCount) { // condition to avoid accumulate progress for repeated chunks + mTransferred += readCount; + } + synchronized (mDataTransferListeners) { + it = mDataTransferListeners.iterator(); + while (it.hasNext()) { + it.next().onTransferProgress(readCount, mTransferred, size, mFile.getName()); + } + } + } + + } catch (IOException io) { + Log.e(TAG, io.getMessage()); + throw new RuntimeException("Ugly solution to workaround the default policy of retries when the server falls while uploading ; temporal fix; really", io); + + } + } + +} diff --combined oc_framework/src/com/owncloud/android/oc_framework/network/webdav/FileRequestEntity.java index 0b1214a0,00000000..3f066f94 mode 100644,000000..100644 --- a/oc_framework/src/com/owncloud/android/oc_framework/network/webdav/FileRequestEntity.java +++ b/oc_framework/src/com/owncloud/android/oc_framework/network/webdav/FileRequestEntity.java @@@ -1,134 -1,0 +1,132 @@@ +/* 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.oc_framework.network.webdav; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.apache.commons.httpclient.methods.RequestEntity; + +import android.util.Log; + +import com.owncloud.android.oc_framework.network.ProgressiveDataTransferer; + + - - +/** + * A RequestEntity that represents a File. + * + */ +public class FileRequestEntity implements RequestEntity, ProgressiveDataTransferer { + + final File mFile; + final String mContentType; + Set mDataTransferListeners = new HashSet(); + + public FileRequestEntity(final File file, final String contentType) { + super(); + this.mFile = file; + this.mContentType = contentType; + if (file == null) { + throw new IllegalArgumentException("File may not be null"); + } + } + + @Override + public long getContentLength() { + return mFile.length(); + } + + @Override + public String getContentType() { + return mContentType; + } + + @Override + public boolean isRepeatable() { + return true; + } + + @Override + public void addDatatransferProgressListener(OnDatatransferProgressListener listener) { + synchronized (mDataTransferListeners) { + mDataTransferListeners.add(listener); + } + } + + @Override + public void addDatatransferProgressListeners(Collection listeners) { + synchronized (mDataTransferListeners) { + mDataTransferListeners.addAll(listeners); + } + } + + @Override + public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) { + synchronized (mDataTransferListeners) { + mDataTransferListeners.remove(listener); + } + } + + + @Override + public void writeRequest(final OutputStream out) throws IOException { + //byte[] tmp = new byte[4096]; + ByteBuffer tmp = ByteBuffer.allocate(4096); + int readResult = 0; + + // TODO(bprzybylski): each mem allocation can throw OutOfMemoryError we need to handle it + // globally in some fashionable manner + RandomAccessFile raf = new RandomAccessFile(mFile, "r"); + FileChannel channel = raf.getChannel(); + Iterator it = null; + long transferred = 0; + long size = mFile.length(); + if (size == 0) size = -1; + try { + while ((readResult = channel.read(tmp)) >= 0) { + out.write(tmp.array(), 0, readResult); + tmp.clear(); + transferred += readResult; + synchronized (mDataTransferListeners) { + it = mDataTransferListeners.iterator(); + while (it.hasNext()) { + it.next().onTransferProgress(readResult, transferred, size, mFile.getName()); + } + } + } + + } catch (IOException io) { + Log.e("FileRequestException", io.getMessage()); + throw new RuntimeException("Ugly solution to workaround the default policy of retries when the server falls while uploading ; temporal fix; really", io); + + } finally { + channel.close(); + raf.close(); + } + } + +} diff --combined oc_framework/src/com/owncloud/android/oc_framework/network/webdav/WebdavClient.java index 72274c9b,00000000..543374cb mode 100644,000000..100644 --- a/oc_framework/src/com/owncloud/android/oc_framework/network/webdav/WebdavClient.java +++ b/oc_framework/src/com/owncloud/android/oc_framework/network/webdav/WebdavClient.java @@@ -1,246 -1,0 +1,245 @@@ +/* 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.oc_framework.network.webdav; + +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.Header; +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.HttpVersion; +import org.apache.commons.httpclient.URI; +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.cookie.CookiePolicy; +import org.apache.commons.httpclient.methods.HeadMethod; +import org.apache.commons.httpclient.params.HttpMethodParams; +import org.apache.http.HttpStatus; +import org.apache.http.params.CoreProtocolPNames; + +import com.owncloud.android.oc_framework.network.BearerAuthScheme; +import com.owncloud.android.oc_framework.network.BearerCredentials; + - +import android.net.Uri; +import android.util.Log; + +public class WebdavClient extends HttpClient { + private static final int MAX_REDIRECTIONS_COUNT = 3; + + private Uri mUri; + private Credentials mCredentials; + private boolean mFollowRedirects; + private String mSsoSessionCookie; + final private static String TAG = WebdavClient.class.getSimpleName(); + public static final String USER_AGENT = "Android-ownCloud"; + + 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); + mFollowRedirects = true; + mSsoSessionCookie = null; + } + + 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); + mSsoSessionCookie = null; + } + + 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); + mSsoSessionCookie = null; + } + + public void setSsoSessionCookie(String accessToken) { + getParams().setAuthenticationPreemptive(false); + getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES); + mSsoSessionCookie = accessToken; + mCredentials = null; + } + + + /** + * Check if a file exists in the OC server + * + * TODO replace with ExistenceOperation + * + * @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); + } + } + + + @Override + public int executeMethod(HttpMethod method) throws IOException, HttpException { + boolean customRedirectionNeeded = false; + try { + method.setFollowRedirects(mFollowRedirects); + } catch (Exception e) { + //if (mFollowRedirects) Log_OC.d(TAG, "setFollowRedirects failed for " + method.getName() + " method, custom redirection will be used if needed"); + customRedirectionNeeded = mFollowRedirects; + } + if (mSsoSessionCookie != null && mSsoSessionCookie.length() > 0) { + method.setRequestHeader("Cookie", mSsoSessionCookie); + } + int status = super.executeMethod(method); + int redirectionsCount = 0; + while (customRedirectionNeeded && + redirectionsCount < MAX_REDIRECTIONS_COUNT && + ( status == HttpStatus.SC_MOVED_PERMANENTLY || + status == HttpStatus.SC_MOVED_TEMPORARILY || + status == HttpStatus.SC_TEMPORARY_REDIRECT) + ) { + + Header location = method.getResponseHeader("Location"); + if (location != null) { + Log.d(TAG, "Location to redirect: " + location.getValue()); + method.setURI(new URI(location.getValue(), true)); + status = super.executeMethod(method); + redirectionsCount++; + + } else { + Log.d(TAG, "No location to redirect!"); + status = HttpStatus.SC_NOT_FOUND; + } + } + + return status; + } + + + /** + * 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); + } + } + } + + /** + * 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; + } + + public final Credentials getCredentials() { + return mCredentials; + } + + public final String getSsoSessionCookie() { + return mSsoSessionCookie; + } + + public void setFollowRedirects(boolean followRedirects) { + mFollowRedirects = followRedirects; + } + +} diff --combined oc_framework/src/com/owncloud/android/oc_framework/network/webdav/WebdavEntry.java index 85a45df9,00000000..3bcfa36a mode 100644,000000..100644 --- a/oc_framework/src/com/owncloud/android/oc_framework/network/webdav/WebdavEntry.java +++ b/oc_framework/src/com/owncloud/android/oc_framework/network/webdav/WebdavEntry.java @@@ -1,149 -1,0 +1,150 @@@ +/* ownCloud Android client application + * Copyright (C) 2012 ownCloud + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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.oc_framework.network.webdav; + +import java.util.Date; + +import org.apache.jackrabbit.webdav.MultiStatusResponse; +import org.apache.jackrabbit.webdav.property.DavProperty; +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.apache.jackrabbit.webdav.property.DavPropertySet; + + ++ +import android.net.Uri; +import android.util.Log; + +public class WebdavEntry { + private String mName, mPath, mUri, mContentType, mEtag; + private long mContentLength, mCreateTimestamp, mModifiedTimestamp; + + public WebdavEntry(MultiStatusResponse ms, String splitElement) { + resetData(); + if (ms.getStatus().length != 0) { + mUri = ms.getHref(); + + mPath = mUri.split(splitElement, 2)[1]; + + int status = ms.getStatus()[0].getStatusCode(); + DavPropertySet propSet = ms.getProperties(status); + @SuppressWarnings("rawtypes") + DavProperty prop = propSet.get(DavPropertyName.DISPLAYNAME); + if (prop != null) { + mName = (String) prop.getName().toString(); + mName = mName.substring(1, mName.length()-1); + } + else { + String[] tmp = mPath.split("/"); + if (tmp.length > 0) + mName = tmp[tmp.length - 1]; + } + + // use unknown mimetype as default behavior + mContentType = "application/octet-stream"; + prop = propSet.get(DavPropertyName.GETCONTENTTYPE); + if (prop != null) { + mContentType = (String) prop.getValue(); + // dvelasco: some builds of ownCloud server 4.0.x added a trailing ';' to the MIME type ; if looks fixed, but let's be cautious + if (mContentType.indexOf(";") >= 0) { + mContentType = mContentType.substring(0, mContentType.indexOf(";")); + } + } + + // check if it's a folder in the standard way: see RFC2518 12.2 . RFC4918 14.3 + prop = propSet.get(DavPropertyName.RESOURCETYPE); + if (prop!= null) { + Object value = prop.getValue(); + if (value != null) { + mContentType = "DIR"; // a specific attribute would be better, but this is enough; unless while we have no reason to distinguish MIME types for folders + } + } + + prop = propSet.get(DavPropertyName.GETCONTENTLENGTH); + if (prop != null) + mContentLength = Long.parseLong((String) prop.getValue()); + + prop = propSet.get(DavPropertyName.GETLASTMODIFIED); + if (prop != null) { + Date d = WebdavUtils + .parseResponseDate((String) prop.getValue()); + mModifiedTimestamp = (d != null) ? d.getTime() : 0; + } + + prop = propSet.get(DavPropertyName.CREATIONDATE); + if (prop != null) { + Date d = WebdavUtils + .parseResponseDate((String) prop.getValue()); + mCreateTimestamp = (d != null) ? d.getTime() : 0; + } + + prop = propSet.get(DavPropertyName.GETETAG); + if (prop != null) { + mEtag = (String) prop.getValue(); + mEtag = mEtag.substring(1, mEtag.length()-1); + } + + } else { + Log.e("WebdavEntry", + "General fuckup, no status for webdav response"); + } + } + + public String path() { + return mPath; + } + + public String decodedPath() { + return Uri.decode(mPath); + } + + public String name() { + return mName; + } + + public boolean isDirectory() { + return mContentType.equals("DIR"); + } + + public String contentType() { + return mContentType; + } + + public String uri() { + return mUri; + } + + public long contentLength() { + return mContentLength; + } + + public long createTimestamp() { + return mCreateTimestamp; + } + + public long modifiedTimestamp() { + return mModifiedTimestamp; + } + + public String etag() { + return mEtag; + } + + private void resetData() { + mName = mUri = mContentType = null; + mContentLength = mCreateTimestamp = mModifiedTimestamp = 0; + } +} diff --combined oc_framework/src/com/owncloud/android/oc_framework/operations/RemoteOperation.java index 6411e162,00000000..e18ae53f mode 100644,000000..100644 --- a/oc_framework/src/com/owncloud/android/oc_framework/operations/RemoteOperation.java +++ b/oc_framework/src/com/owncloud/android/oc_framework/operations/RemoteOperation.java @@@ -1,286 -1,0 +1,287 @@@ +/* 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.oc_framework.operations; + +import java.io.IOException; + +import org.apache.commons.httpclient.Credentials; + +import com.owncloud.android.oc_framework.network.BearerCredentials; +import com.owncloud.android.oc_framework.network.webdav.WebdavClient; +import com.owncloud.android.oc_framework.network.webdav.OwnCloudClientFactory; +import com.owncloud.android.oc_framework.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; + + +/** + * Operation which execution involves one or several interactions with an ownCloud server. + * + * Provides methods to execute the operation both synchronously or asynchronously. + * + * @author David A. Velasco + */ +public abstract class RemoteOperation implements Runnable { + + private static final String TAG = RemoteOperation.class.getSimpleName(); + + /** ownCloud account in the remote ownCloud server to operate */ + private Account mAccount = null; + + /** Android Application context */ + private Context mContext = null; + + /** Object to interact with the remote server */ + private WebdavClient mClient = null; + + /** Callback object to notify about the execution of the remote operation */ + private OnRemoteOperationListener mListener = null; + + /** Handler to the thread where mListener methods will be called */ + private Handler mListenerHandler = null; + + /** Activity */ + private Activity mCallerActivity; + + + /** + * Abstract method to implement the operation in derived classes. + */ + protected abstract RemoteOperationResult run(WebdavClient client); + + + /** + * Synchronously executes the remote operation on the received ownCloud account. + * + * Do not call this method from the main thread. + * + * This method should be used whenever an ownCloud account is available, instead of {@link #execute(WebdavClient)}. + * + * @param account ownCloud account in remote ownCloud server to reach during the execution of the operation. + * @param context Android context for the component calling the method. + * @return Result of the operation. + */ + public final RemoteOperationResult execute(Account account, Context context) { + if (account == null) + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Account"); + if (context == null) + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Context"); + mAccount = account; + mContext = context.getApplicationContext(); + try { + mClient = OwnCloudClientFactory.createOwnCloudClient(mAccount, mContext); + } catch (Exception e) { + Log.e(TAG, "Error while trying to access to " + mAccount.name, e); + return new RemoteOperationResult(e); + } + return run(mClient); + } + + + /** + * Synchronously executes the remote operation + * + * Do not call this method from the main thread. + * + * @param client Client object to reach an ownCloud server during the execution of the operation. + * @return Result of the operation. + */ + public final RemoteOperationResult execute(WebdavClient client) { + if (client == null) + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient"); + mClient = client; + return run(client); + } + + + /** + * Asynchronously executes the remote operation + * + * This method should be used whenever an ownCloud account is available, instead of {@link #execute(WebdavClient)}. + * + * @param account ownCloud account in remote ownCloud server to reach during the execution of the operation. + * @param context Android context for the component calling the method. + * @param listener Listener to be notified about the execution of the operation. + * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. + * @return Thread were the remote operation is executed. + */ + public final Thread execute(Account account, Context context, OnRemoteOperationListener listener, Handler listenerHandler, Activity callerActivity) { + if (account == null) + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Account"); + if (context == null) + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL Context"); + mAccount = account; + mContext = context.getApplicationContext(); + mCallerActivity = callerActivity; + mClient = null; // the client instance will be created from mAccount and mContext in the runnerThread to create below + + mListener = listener; + + mListenerHandler = listenerHandler; + + Thread runnerThread = new Thread(this); + runnerThread.start(); + return runnerThread; + } + + + /** + * Asynchronously executes the remote operation + * + * @param client Client object to reach an ownCloud server during the execution of the operation. + * @param listener Listener to be notified about the execution of the operation. + * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. + * @return Thread were the remote operation is executed. + */ + public final Thread execute(WebdavClient client, OnRemoteOperationListener listener, Handler listenerHandler) { + if (client == null) { + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient"); + } + mClient = client; + + if (listener == null) { + throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a listener to notiy the result"); + } + mListener = listener; + + if (listenerHandler == null) { + throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a handler to the listener's thread"); + } + mListenerHandler = listenerHandler; + + Thread runnerThread = new Thread(this); + runnerThread.start(); + return runnerThread; + } + + /** + * Synchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient)} + * + * @param listener Listener to be notified about the execution of the operation. + * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. + * @return Thread were the remote operation is executed. + */ + public final RemoteOperationResult retry() { + return execute(mClient); + } + + /** + * Asynchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)} + * + * @param listener Listener to be notified about the execution of the operation. + * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. + * @return Thread were the remote operation is executed. + */ + public final Thread retry(OnRemoteOperationListener listener, Handler listenerHandler) { + return execute(mClient, listener, listenerHandler); + } + + + /** + * Asynchronous execution of the operation + * started by {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)}, + * and result posting. + * + * TODO refactor && clean the code; now it's a mess + */ + @Override + public final void run() { + RemoteOperationResult result = null; + boolean repeat = false; + do { + try{ + if (mClient == null) { + if (mAccount != null && mContext != null) { + if (mCallerActivity != null) { + mClient = OwnCloudClientFactory.createOwnCloudClient(mAccount, mContext, mCallerActivity); + } else { + mClient = OwnCloudClientFactory.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)); + result = new RemoteOperationResult(e); + + } catch (AccountsException e) { + Log.e(TAG, "Error while trying to access to " + mAccount.name, e); + result = new RemoteOperationResult(e); + } + + if (result == null) + result = run(mClient); + + repeat = false; + if (mCallerActivity != null && mAccount != null && mContext != null && !result.isSuccess() && +// (result.getCode() == ResultCode.UNAUTHORIZED || (result.isTemporalRedirection() && result.isIdPRedirection()))) { + (result.getCode() == ResultCode.UNAUTHORIZED || result.isIdPRedirection())) { + /// possible fail due to lack of authorization in an operation performed in foreground + Credentials cred = mClient.getCredentials(); + String ssoSessionCookie = mClient.getSsoSessionCookie(); + if (cred != null || ssoSessionCookie != null) { + /// confirmed : unauthorized operation + AccountManager am = AccountManager.get(mContext); + boolean bearerAuthorization = (cred != null && cred instanceof BearerCredentials); + boolean samlBasedSsoAuthorization = (cred == null && ssoSessionCookie != null); + if (bearerAuthorization) { + am.invalidateAuthToken(mAccount.type, ((BearerCredentials)cred).getAccessToken()); + } else if (samlBasedSsoAuthorization ) { + am.invalidateAuthToken(mAccount.type, ssoSessionCookie); + } else { + am.clearPassword(mAccount); + } + mClient = null; + repeat = true; // when repeated, the creation of a new OwnCloudClient after erasing the saved credentials will trigger the login activity + result = null; + } + } + } while (repeat); + + final RemoteOperationResult resultToSend = result; + if (mListenerHandler != null && mListener != null) { + mListenerHandler.post(new Runnable() { + @Override + public void run() { + mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend); + } + }); + } + } + + + /** + * Returns the current client instance to access the remote server. + * + * @return Current client instance to access the remote server. + */ + public final WebdavClient getClient() { + return mClient; + } + + +} diff --combined project.properties index f16bf830,c9e58408..dbd0f5e5 --- a/project.properties +++ b/project.properties @@@ -8,6 -8,5 +8,6 @@@ # project structure. # Project target. - target=android-17 - android.library.reference.1=actionbarsherlock\\library + target=android-19 + android.library.reference.1=actionbarsherlock/library +android.library.reference.2=oc_framework diff --combined src/com/owncloud/android/authentication/AccountAuthenticator.java index d0b5a245,ff0782d5..58a13521 --- a/src/com/owncloud/android/authentication/AccountAuthenticator.java +++ b/src/com/owncloud/android/authentication/AccountAuthenticator.java @@@ -18,6 -18,10 +18,10 @@@ package com.owncloud.android.authentication; + import com.owncloud.android.Log_OC; + import com.owncloud.android.MainApp; + import com.owncloud.android.R; + import android.accounts.*; import android.content.Context; import android.content.Intent; @@@ -25,11 -29,7 +29,7 @@@ import android.os.Bundle import android.os.Handler; import android.widget.Toast; - import com.owncloud.android.Log_OC; - import com.owncloud.android.MainApp; -- - import com.owncloud.android.R; +import com.owncloud.android.oc_framework.accounts.AccountTypeUtils; /** @@@ -53,6 -53,34 +53,6 @@@ public class AccountAuthenticator exten public static final String KEY_LOGIN_OPTIONS = "loginOptions"; public static final String KEY_ACCOUNT = "account"; - /** - * Value under this key should handle path to webdav php script. Will be - * removed and usage should be replaced by combining - * {@link com.owncloud.android.authentication.AuthenticatorActivity.KEY_OC_BASE_URL} and - * {@link com.owncloud.android.utils.OwnCloudVersion} - * - * @deprecated - */ - public static final String KEY_OC_URL = "oc_url"; - /** - * Version should be 3 numbers separated by dot so it can be parsed by - * {@link com.owncloud.android.utils.OwnCloudVersion} - */ - public static final String KEY_OC_VERSION = "oc_version"; - /** - * Base url should point to owncloud installation without trailing / ie: - * http://server/path or https://owncloud.server - */ - public static final String KEY_OC_BASE_URL = "oc_base_url"; - /** - * Flag signaling if the ownCloud server can be accessed with OAuth2 access tokens. - */ - public static final String KEY_SUPPORTS_OAUTH2 = "oc_supports_oauth2"; - /** - * Flag signaling if the ownCloud server can be accessed with session cookies from SAML-based web single-sign-on. - */ - public static final String KEY_SUPPORTS_SAML_WEB_SSO = "oc_supports_saml_web_sso"; - private static final String TAG = AccountAuthenticator.class.getSimpleName(); private Context mContext; @@@ -176,7 -204,7 +176,7 @@@ /// check if required token is stored final AccountManager am = AccountManager.get(mContext); String accessToken; - if (authTokenType.equals(MainApp.getAuthTokenTypePass())) { + if (authTokenType.equals(AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType()))) { accessToken = am.getPassword(account); } else { accessToken = am.peekAuthToken(account, authTokenType); @@@ -257,10 -285,10 +257,10 @@@ private void validateAuthTokenType(String authTokenType) throws UnsupportedAuthTokenTypeException { if (!authTokenType.equals(MainApp.getAuthTokenType()) && - !authTokenType.equals(MainApp.getAuthTokenTypePass()) && - !authTokenType.equals(MainApp.getAuthTokenTypeAccessToken()) && - !authTokenType.equals(MainApp.getAuthTokenTypeRefreshToken()) && - !authTokenType.equals(MainApp.getAuthTokenTypeSamlSessionCookie())) { + !authTokenType.equals(AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType())) && + !authTokenType.equals(AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType())) && + !authTokenType.equals(AccountTypeUtils.getAuthTokenTypeRefreshToken(MainApp.getAccountType())) && + !authTokenType.equals(AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()))) { throw new UnsupportedAuthTokenTypeException(); } } diff --combined src/com/owncloud/android/authentication/AuthenticatorActivity.java index a656873f,faeffd53..105d644f --- a/src/com/owncloud/android/authentication/AuthenticatorActivity.java +++ b/src/com/owncloud/android/authentication/AuthenticatorActivity.java @@@ -55,23 -55,22 +55,22 @@@ import com.owncloud.android.Log_OC import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.authentication.SsoWebViewClient.SsoWebViewClientListener; -import com.owncloud.android.network.OwnCloudClientUtils; +import com.owncloud.android.oc_framework.accounts.AccountTypeUtils; +import com.owncloud.android.oc_framework.accounts.OwnCloudAccount; +import com.owncloud.android.oc_framework.network.webdav.OwnCloudClientFactory; +import com.owncloud.android.oc_framework.network.webdav.WebdavClient; import com.owncloud.android.operations.ExistenceCheckOperation; import com.owncloud.android.operations.OAuth2GetAccessToken; -import com.owncloud.android.operations.OnRemoteOperationListener; +import com.owncloud.android.oc_framework.operations.OnRemoteOperationListener; import com.owncloud.android.operations.OwnCloudServerCheckOperation; -import com.owncloud.android.operations.RemoteOperation; -import com.owncloud.android.operations.RemoteOperationResult; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.oc_framework.operations.RemoteOperation; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.ui.dialog.SamlWebViewDialog; import com.owncloud.android.ui.dialog.SslValidatorDialog; import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener; -import com.owncloud.android.utils.OwnCloudVersion; - - -import eu.alefzero.webdav.WebdavClient; +import com.owncloud.android.oc_framework.utils.OwnCloudVersion; - /** * This Activity is used to add an ownCloud account to the App * @@@ -194,11 -193,16 +193,16 @@@ implements OnRemoteOperationListener, /// set Host Url Input Enabled mHostUrlInputEnabled = getResources().getBoolean(R.bool.show_server_url_input); - - /// complete label for 'register account' button - Button b = (Button) findViewById(R.id.account_register); - if (b != null) { - b.setText(String.format(getString(R.string.auth_register), getString(R.string.app_name))); + /// set visibility of link for new users + boolean accountRegisterVisibility = getResources().getBoolean(R.bool.show_welcome_link); + Button welcomeLink = (Button) findViewById(R.id.welcome_link); + if (welcomeLink != null) { + if (accountRegisterVisibility) { + welcomeLink.setVisibility(View.VISIBLE); + welcomeLink.setText(String.format(getString(R.string.auth_register), getString(R.string.app_name))); + } else { + findViewById(R.id.welcome_link).setVisibility(View.GONE); + } } /// initialization @@@ -232,11 -236,11 +236,11 @@@ /// retrieve extras from intent mAccount = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT); if (mAccount != null) { - String ocVersion = mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION); + String ocVersion = mAccountMgr.getUserData(mAccount, OwnCloudAccount.Constants.KEY_OC_VERSION); if (ocVersion != null) { mDiscoveredVersion = new OwnCloudVersion(ocVersion); } - mHostBaseUrl = normalizeUrl(mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL)); + mHostBaseUrl = normalizeUrl(mAccountMgr.getUserData(mAccount, OwnCloudAccount.Constants.KEY_OC_BASE_URL)); mHostUrlInput.setText(mHostBaseUrl); String userName = mAccount.name.substring(0, mAccount.name.lastIndexOf('@')); mUsernameInput.setText(userName); @@@ -275,7 -279,7 +279,7 @@@ mAccount = savedInstanceState.getParcelable(KEY_ACCOUNT); mAuthTokenType = savedInstanceState.getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE); if (mAuthTokenType == null) { - mAuthTokenType = MainApp.getAuthTokenTypePass(); + mAuthTokenType = AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType()); } @@@ -313,7 -317,7 +317,7 @@@ if (mServerIsChecked && !mServerIsValid && refreshButtonEnabled) showRefreshButton(); mOkButton.setEnabled(mServerIsValid); // state not automatically recovered in configuration changes - if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType) || + if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType) || !AUTH_OPTIONAL.equals(getString(R.string.auth_method_oauth2))) { mOAuth2Check.setVisibility(View.GONE); } @@@ -365,7 -369,7 +369,7 @@@ @Override public boolean onTouch(View view, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType) && + if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType) && mHostUrlInput.hasFocus()) { checkOcServer(); } @@@ -389,8 -393,8 +393,8 @@@ if (mAuthTokenType == null) { if (mAccount != null) { /// same authentication method than the one used to create the account to update - oAuthRequired = (mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null); - samlWebSsoRequired = (mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_SAML_WEB_SSO) != null); + oAuthRequired = (mAccountMgr.getUserData(mAccount, OwnCloudAccount.Constants.KEY_SUPPORTS_OAUTH2) != null); + samlWebSsoRequired = (mAccountMgr.getUserData(mAccount, OwnCloudAccount.Constants.KEY_SUPPORTS_SAML_WEB_SSO) != null); } else { /// use the one set in setup.xml @@@ -398,11 -402,11 +402,11 @@@ samlWebSsoRequired = AUTH_ON.equals(getString(R.string.auth_method_saml_web_sso)); } if (oAuthRequired) { - mAuthTokenType = MainApp.getAuthTokenTypeAccessToken(); + mAuthTokenType = AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()); } else if (samlWebSsoRequired) { - mAuthTokenType = MainApp.getAuthTokenTypeSamlSessionCookie(); + mAuthTokenType = AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()); } else { - mAuthTokenType = MainApp.getAuthTokenTypePass(); + mAuthTokenType = AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType()); } } @@@ -411,7 -415,7 +415,7 @@@ mUsernameInput.setText(userName); } - mOAuth2Check.setChecked(MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType)); + mOAuth2Check.setChecked(AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType)); } @@@ -484,10 -488,10 +488,10 @@@ protected void onResume() { super.onResume(); if (mAction == ACTION_UPDATE_TOKEN && mJustCreated && getIntent().getBooleanExtra(EXTRA_ENFORCED_UPDATE, false)) { - if (MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType)) { + if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType)) { //Toast.makeText(this, R.string.auth_expired_oauth_token_toast, Toast.LENGTH_LONG).show(); showAuthMessage(getString(R.string.auth_expired_oauth_token_toast)); - } else if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType)) { + } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) { //Toast.makeText(this, R.string.auth_expired_saml_sso_token_toast, Toast.LENGTH_LONG).show(); showAuthMessage(getString(R.string.auth_expired_saml_sso_token_toast)); } else { @@@ -523,7 -527,7 +527,7 @@@ getString(R.string.oauth2_grant_type), queryParameters); //WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(getString(R.string.oauth2_url_endpoint_access)), getApplicationContext()); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mOAuthTokenEndpointText.getText().toString().trim()), getApplicationContext(), true); + WebdavClient client = OwnCloudClientFactory.createOwnCloudClient(Uri.parse(mOAuthTokenEndpointText.getText().toString().trim()), getApplicationContext(), true); operation.execute(client, this, mHandler); } @@@ -587,7 -591,7 +591,7 @@@ mServerStatusIcon = R.drawable.progress_small; showServerStatus(); mOcServerChkOperation = new OwnCloudServerCheckOperation(uri, this); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(uri), this, true); + WebdavClient client = OwnCloudClientFactory.createOwnCloudClient(Uri.parse(uri), this, true); mOperationThread = mOcServerChkOperation.execute(client, this, mHandler); } else { mServerStatusText = 0; @@@ -687,9 -691,9 +691,9 @@@ return; } - if (MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType)) { + if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType)) { startOauthorization(); - } else if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType)) { + } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) { startSamlBasedFederatedSingleSignOnAuthorization(); } else { checkBasicAuthorization(); @@@ -714,7 -718,7 +718,7 @@@ /// test credentials accessing the root folder mAuthCheckOperation = new ExistenceCheckOperation("", this, false); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, true); + WebdavClient client = OwnCloudClientFactory.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, true); client.setBasicCredentials(username, password); mOperationThread = mAuthCheckOperation.execute(client, this, mHandler); } @@@ -763,7 -767,7 +767,7 @@@ /// test credentials accessing the root folder mAuthCheckOperation = new ExistenceCheckOperation("", this, false); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, false); + WebdavClient client = OwnCloudClientFactory.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, false); mOperationThread = mAuthCheckOperation.execute(client, this, mHandler); } @@@ -783,7 -787,7 +787,7 @@@ onGetOAuthAccessTokenFinish((OAuth2GetAccessToken)operation, result); } else if (operation instanceof ExistenceCheckOperation) { - if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType)) { + if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) { onSamlBasedFederatedSingleSignOnAuthorizationStart(operation, result); } else { @@@ -1082,7 -1086,7 +1086,7 @@@ mAuthToken = ((OAuth2GetAccessToken)operation).getResultTokenMap().get(OAuth2Constants.KEY_ACCESS_TOKEN); Log_OC.d(TAG, "Got ACCESS TOKEN: " + mAuthToken); mAuthCheckOperation = new ExistenceCheckOperation("", this, false); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, true); + WebdavClient client = OwnCloudClientFactory.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, true); client.setBearerCredentials(mAuthToken); mAuthCheckOperation.execute(client, this, mHandler); @@@ -1168,12 -1172,12 +1172,12 @@@ response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name); response.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type); - if (MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType)) { + if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType)) { response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken); // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken); - } else if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType)) { + } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) { String username = getUserNameForSamlSso(); if (!mUsernameInput.getText().toString().equals(username)) { // fail - not a new account, but an existing one; disallow @@@ -1208,8 -1212,8 +1212,8 @@@ */ private boolean createAccount() { /// create and save new ownCloud account - boolean isOAuth = MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType); - boolean isSaml = MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType); + boolean isOAuth = AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType); + boolean isSaml = AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType); Uri uri = Uri.parse(mHostBaseUrl); String username = mUsernameInput.getText().toString().trim(); @@@ -1261,25 -1265,17 +1265,17 @@@ mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken); } /// add user data to the new account; TODO probably can be done in the last parameter addAccountExplicitly, or in KEY_USERDATA - mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION, mDiscoveredVersion.toString()); - mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL, mHostBaseUrl); + mAccountMgr.setUserData(mAccount, OwnCloudAccount.Constants.KEY_OC_VERSION, mDiscoveredVersion.toString()); + mAccountMgr.setUserData(mAccount, OwnCloudAccount.Constants.KEY_OC_BASE_URL, mHostBaseUrl); if (isSaml) { - mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_SAML_WEB_SSO, "TRUE"); + mAccountMgr.setUserData(mAccount, OwnCloudAccount.Constants.KEY_SUPPORTS_SAML_WEB_SSO, "TRUE"); } else if (isOAuth) { - mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2, "TRUE"); + mAccountMgr.setUserData(mAccount, OwnCloudAccount.Constants.KEY_SUPPORTS_OAUTH2, "TRUE"); } setAccountAuthenticatorResult(intent.getExtras()); setResult(RESULT_OK, intent); - /// immediately request for the synchronization of the new account - Bundle bundle = new Bundle(); - bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - ContentResolver.requestSync(mAccount, MainApp.getAuthTokenType(), bundle); - syncAccount(); - // Bundle bundle = new Bundle(); - // bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - // ContentResolver.requestSync(mAccount, MainApp.getAuthTokenType(), bundle); return true; } } @@@ -1398,7 -1394,7 +1394,7 @@@ * @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))); + Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.welcome_link_url))); setResult(RESULT_CANCELED); startActivity(register); } @@@ -1486,9 -1482,9 +1482,9 @@@ public void onCheckClick(View view) { CheckBox oAuth2Check = (CheckBox)view; if (oAuth2Check.isChecked()) { - mAuthTokenType = MainApp.getAuthTokenTypeAccessToken(); + mAuthTokenType = AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()); } else { - mAuthTokenType = MainApp.getAuthTokenTypePass(); + mAuthTokenType = AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType()); } adaptViewAccordingToAuthenticationMethod(); } @@@ -1499,14 -1495,14 +1495,14 @@@ * the current authorization method. */ private void adaptViewAccordingToAuthenticationMethod () { - if (MainApp.getAuthTokenTypeAccessToken().equals(mAuthTokenType)) { + if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType)) { // OAuth 2 authorization mOAuthAuthEndpointText.setVisibility(View.VISIBLE); mOAuthTokenEndpointText.setVisibility(View.VISIBLE); mUsernameInput.setVisibility(View.GONE); mPasswordInput.setVisibility(View.GONE); - } else if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType)) { + } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) { // SAML-based web Single Sign On mOAuthAuthEndpointText.setVisibility(View.GONE); mOAuthTokenEndpointText.setVisibility(View.GONE); @@@ -1552,7 -1548,7 +1548,7 @@@ } } else if (actionId == EditorInfo.IME_ACTION_NEXT && inputField != null && inputField.equals(mHostUrlInput)) { - if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType)) { + if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) { checkOcServer(); } } @@@ -1649,16 -1645,9 +1645,9 @@@ mAuthMessage.setVisibility(View.GONE); } - private void syncAccount(){ - /// immediately request for the synchronization of the new account - Bundle bundle = new Bundle(); - bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - ContentResolver.requestSync(mAccount, MainApp.getAuthTokenType(), bundle); - } - @Override public boolean onTouchEvent(MotionEvent event) { - if (MainApp.getAuthTokenTypeSamlSessionCookie().equals(mAuthTokenType) && + if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType) && mHostUrlInput.hasFocus() && event.getAction() == MotionEvent.ACTION_DOWN) { checkOcServer(); } diff --combined src/com/owncloud/android/files/OwnCloudFileObserver.java index 4d441953,d887256e..9b3c8427 --- a/src/com/owncloud/android/files/OwnCloudFileObserver.java +++ b/src/com/owncloud/android/files/OwnCloudFileObserver.java @@@ -23,12 -23,13 +23,13 @@@ import java.io.File import com.owncloud.android.Log_OC; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult; import com.owncloud.android.operations.SynchronizeFileOperation; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.ui.activity.ConflictsResolveActivity; + import android.accounts.Account; import android.content.Context; import android.content.Intent; diff --combined src/com/owncloud/android/files/services/FileDownloader.java index 313a8d12,2e479f4d..266cb650 --- a/src/com/owncloud/android/files/services/FileDownloader.java +++ b/src/com/owncloud/android/files/services/FileDownloader.java @@@ -28,21 -28,24 +28,23 @@@ import java.util.Vector import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; + import com.owncloud.android.Log_OC; -import com.owncloud.android.MainApp; + import com.owncloud.android.R; import com.owncloud.android.authentication.AuthenticatorActivity; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.network.OwnCloudClientUtils; + +import com.owncloud.android.oc_framework.network.webdav.OnDatatransferProgressListener; +import com.owncloud.android.oc_framework.network.webdav.OwnCloudClientFactory; +import com.owncloud.android.oc_framework.network.webdav.WebdavClient; import com.owncloud.android.operations.DownloadFileOperation; -import com.owncloud.android.operations.RemoteOperationResult; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.preview.PreviewImageActivity; import com.owncloud.android.ui.preview.PreviewImageFragment; -import eu.alefzero.webdav.OnDatatransferProgressListener; - - import android.accounts.Account; import android.accounts.AccountsException; import android.app.Notification; @@@ -59,9 -62,8 +61,6 @@@ 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"; @@@ -353,7 -355,7 +352,7 @@@ if (mDownloadClient == null || !mLastAccount.equals(mCurrentDownload.getAccount())) { mLastAccount = mCurrentDownload.getAccount(); mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver()); - mDownloadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext()); + mDownloadClient = OwnCloudClientFactory.createOwnCloudClient(mLastAccount, getApplicationContext()); } /// perform the download @@@ -474,8 -476,7 +473,8 @@@ boolean needsToUpdateCredentials = (downloadResult.getCode() == ResultCode.UNAUTHORIZED || // (downloadResult.isTemporalRedirection() && downloadResult.isIdPRedirection() (downloadResult.isIdPRedirection() - && MainApp.getAuthTokenTypeSamlSessionCookie().equals(mDownloadClient.getAuthTokenType()))); + && mDownloadClient.getCredentials() == null)); + //&& MainApp.getAuthTokenTypeSamlSessionCookie().equals(mDownloadClient.getAuthTokenType()))); if (needsToUpdateCredentials) { // let the user update credentials with one click Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class); diff --combined src/com/owncloud/android/files/services/FileUploader.java index c149fd49,7f45b6b9..4768911e --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploader.java @@@ -33,25 -33,35 +33,34 @@@ import org.apache.jackrabbit.webdav.Dav import org.apache.jackrabbit.webdav.MultiStatus; import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; + import com.owncloud.android.Log_OC; + import com.owncloud.android.MainApp; + import com.owncloud.android.R; import com.owncloud.android.authentication.AccountAuthenticator; import com.owncloud.android.authentication.AuthenticatorActivity; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; + import com.owncloud.android.db.DbHandler; -import com.owncloud.android.network.OwnCloudClientUtils; import com.owncloud.android.operations.ChunkedUploadFileOperation; import com.owncloud.android.operations.CreateFolderOperation; import com.owncloud.android.operations.ExistenceCheckOperation; -import com.owncloud.android.operations.RemoteOperation; -import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.oc_framework.operations.RemoteOperation; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult; import com.owncloud.android.operations.UploadFileOperation; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.oc_framework.utils.OwnCloudVersion; - +import com.owncloud.android.oc_framework.network.webdav.OnDatatransferProgressListener; +import com.owncloud.android.oc_framework.accounts.OwnCloudAccount; +import com.owncloud.android.oc_framework.network.webdav.OwnCloudClientFactory; +import com.owncloud.android.oc_framework.network.webdav.WebdavClient; +import com.owncloud.android.oc_framework.network.webdav.WebdavEntry; +import com.owncloud.android.oc_framework.network.webdav.WebdavUtils; + import com.owncloud.android.ui.activity.FailedUploadActivity; + import com.owncloud.android.ui.activity.FileActivity; + import com.owncloud.android.ui.activity.FileDisplayActivity; + import com.owncloud.android.ui.activity.InstantUploadActivity; + import com.owncloud.android.ui.preview.PreviewImageActivity; + import com.owncloud.android.ui.preview.PreviewImageFragment; -import com.owncloud.android.utils.OwnCloudVersion; - - -import eu.alefzero.webdav.OnDatatransferProgressListener; -import eu.alefzero.webdav.WebdavEntry; -import eu.alefzero.webdav.WebdavUtils; - import android.accounts.Account; import android.accounts.AccountManager; @@@ -71,17 -81,8 +80,7 @@@ import android.os.Process import android.webkit.MimeTypeMap; import android.widget.RemoteViews; - import com.owncloud.android.Log_OC; - import com.owncloud.android.MainApp; - import com.owncloud.android.R; - import com.owncloud.android.db.DbHandler; - import com.owncloud.android.ui.activity.FailedUploadActivity; - import com.owncloud.android.ui.activity.FileActivity; - import com.owncloud.android.ui.activity.FileDisplayActivity; - import com.owncloud.android.ui.activity.InstantUploadActivity; - import com.owncloud.android.ui.preview.PreviewImageActivity; - import com.owncloud.android.ui.preview.PreviewImageFragment; -import eu.alefzero.webdav.WebdavClient; public class FileUploader extends Service implements OnDatatransferProgressListener { @@@ -260,7 -261,8 +259,7 @@@ } } - OwnCloudVersion ocv = new OwnCloudVersion(AccountManager.get(this).getUserData(account, - AccountAuthenticator.KEY_OC_VERSION)); + OwnCloudVersion ocv = new OwnCloudVersion(AccountManager.get(this).getUserData(account, OwnCloudAccount.Constants.KEY_OC_VERSION)); boolean chunked = FileUploader.chunkedUploadIsSupported(ocv); AbstractList requestedUploads = new Vector(); String uploadKey = null; @@@ -502,7 -504,7 +501,7 @@@ if (mUploadClient == null || !mLastAccount.equals(mCurrentUpload.getAccount())) { mLastAccount = mCurrentUpload.getAccount(); mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver()); - mUploadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext()); + mUploadClient = OwnCloudClientFactory.createOwnCloudClient(mLastAccount, getApplicationContext()); } /// check the existence of the parent folder for the file to upload @@@ -821,8 -823,7 +820,8 @@@ boolean needsToUpdateCredentials = (uploadResult.getCode() == ResultCode.UNAUTHORIZED || //(uploadResult.isTemporalRedirection() && uploadResult.isIdPRedirection() && (uploadResult.isIdPRedirection() && - MainApp.getAuthTokenTypeSamlSessionCookie().equals(mUploadClient.getAuthTokenType()))); + mUploadClient.getCredentials() == null)); + //MainApp.getAuthTokenTypeSamlSessionCookie().equals(mUploadClient.getAuthTokenType()))); if (needsToUpdateCredentials) { // let the user update credentials with one click Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class); diff --combined src/com/owncloud/android/operations/ChunkedUploadFileOperation.java index 3da5f349,2649d197..ad09615a --- a/src/com/owncloud/android/operations/ChunkedUploadFileOperation.java +++ b/src/com/owncloud/android/operations/ChunkedUploadFileOperation.java @@@ -29,13 -29,14 +29,14 @@@ import org.apache.commons.httpclient.me import com.owncloud.android.Log_OC; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.network.ProgressiveDataTransferer; +import com.owncloud.android.oc_framework.network.ProgressiveDataTransferer; +import com.owncloud.android.oc_framework.network.webdav.ChunkFromFileChannelRequestEntity; +import com.owncloud.android.oc_framework.network.webdav.WebdavClient; +import com.owncloud.android.oc_framework.network.webdav.WebdavUtils; + import android.accounts.Account; -import eu.alefzero.webdav.ChunkFromFileChannelRequestEntity; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; public class ChunkedUploadFileOperation extends UploadFileOperation { diff --combined src/com/owncloud/android/operations/RenameFileOperation.java index 1cd52dd6,a2370f3b..e6828fea --- a/src/com/owncloud/android/operations/RenameFileOperation.java +++ b/src/com/owncloud/android/operations/RenameFileOperation.java @@@ -21,20 -21,19 +21,21 @@@ import java.io.File import java.io.IOException; import org.apache.jackrabbit.webdav.client.methods.DavMethodBase; - //import org.apache.jackrabbit.webdav.client.methods.MoveMethod; - - import android.accounts.Account; import com.owncloud.android.Log_OC; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.oc_framework.network.webdav.WebdavClient; +import com.owncloud.android.oc_framework.network.webdav.WebdavUtils; +import com.owncloud.android.oc_framework.operations.RemoteOperation; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.utils.FileStorageUtils; + //import org.apache.jackrabbit.webdav.client.methods.MoveMethod; + + import android.accounts.Account; + -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; /** * Remote operation performing the rename of a remote file (or folder?) in the ownCloud server. diff --combined src/com/owncloud/android/operations/SynchronizeFileOperation.java index c6641a83,2e15c2fe..5f77a86b --- a/src/com/owncloud/android/operations/SynchronizeFileOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFileOperation.java @@@ -23,23 -23,22 +23,23 @@@ import org.apache.jackrabbit.webdav.Dav import org.apache.jackrabbit.webdav.MultiStatus; import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; - import android.accounts.Account; - import android.content.Context; - import android.content.Intent; - import com.owncloud.android.Log_OC; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader; import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.oc_framework.network.webdav.WebdavClient; +import com.owncloud.android.oc_framework.network.webdav.WebdavEntry; +import com.owncloud.android.oc_framework.network.webdav.WebdavUtils; +import com.owncloud.android.oc_framework.operations.RemoteOperation; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult.ResultCode; + import android.accounts.Account; + import android.content.Context; + import android.content.Intent; + -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavEntry; -import eu.alefzero.webdav.WebdavUtils; - public class SynchronizeFileOperation extends RemoteOperation { private String TAG = SynchronizeFileOperation.class.getSimpleName(); diff --combined src/com/owncloud/android/operations/UpdateOCVersionOperation.java index 4db6363f,2e116ac6..9cc7370b --- a/src/com/owncloud/android/operations/UpdateOCVersionOperation.java +++ b/src/com/owncloud/android/operations/UpdateOCVersionOperation.java @@@ -22,21 -22,19 +22,20 @@@ import org.apache.commons.httpclient.me import org.json.JSONException; import org.json.JSONObject; - import android.accounts.Account; - import android.accounts.AccountManager; - import android.content.Context; - - import com.owncloud.android.authentication.AccountAuthenticator; - import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.Log_OC; -import com.owncloud.android.authentication.AccountAuthenticator; + import com.owncloud.android.authentication.AccountUtils; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; -import com.owncloud.android.utils.OwnCloudVersion; +import com.owncloud.android.oc_framework.accounts.OwnCloudAccount; +import com.owncloud.android.oc_framework.network.webdav.WebdavClient; +import com.owncloud.android.oc_framework.operations.RemoteOperation; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.oc_framework.utils.OwnCloudVersion; + import android.accounts.Account; + import android.accounts.AccountManager; + import android.content.Context; + -import eu.alefzero.webdav.WebdavClient; - /** * Remote operation that checks the version of an ownCloud server and stores it locally * @@@ -59,7 -57,7 +58,7 @@@ public class UpdateOCVersionOperation e @Override protected RemoteOperationResult run(WebdavClient client) { AccountManager accountMngr = AccountManager.get(mContext); - String statUrl = accountMngr.getUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL); + String statUrl = accountMngr.getUserData(mAccount, OwnCloudAccount.Constants.KEY_OC_BASE_URL); statUrl += AccountUtils.STATUS_PATH; RemoteOperationResult result = null; GetMethod get = null; @@@ -77,7 -75,7 +76,7 @@@ if (json != null && json.getString("version") != null) { OwnCloudVersion ocver = new OwnCloudVersion(json.getString("version")); if (ocver.isVersionValid()) { - accountMngr.setUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION, ocver.toString()); + accountMngr.setUserData(mAccount, OwnCloudAccount.Constants.KEY_OC_VERSION, ocver.toString()); Log_OC.d(TAG, "Got new OC version " + ocver.toString()); result = new RemoteOperationResult(ResultCode.OK); diff --combined src/com/owncloud/android/operations/UploadFileOperation.java index e0ef1874,936ac01e..bc49af23 --- a/src/com/owncloud/android/operations/UploadFileOperation.java +++ b/src/com/owncloud/android/operations/UploadFileOperation.java @@@ -33,22 -33,22 +33,22 @@@ import org.apache.commons.httpclient.me import org.apache.http.HttpStatus; import com.owncloud.android.Log_OC; - import android.accounts.Account; - import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.network.ProgressiveDataTransferer; -import com.owncloud.android.operations.RemoteOperation; -import com.owncloud.android.operations.RemoteOperationResult; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.oc_framework.network.ProgressiveDataTransferer; +import com.owncloud.android.oc_framework.network.webdav.FileRequestEntity; +import com.owncloud.android.oc_framework.network.webdav.OnDatatransferProgressListener; +import com.owncloud.android.oc_framework.network.webdav.WebdavClient; +import com.owncloud.android.oc_framework.network.webdav.WebdavUtils; +import com.owncloud.android.oc_framework.operations.OperationCancelledException; +import com.owncloud.android.oc_framework.operations.RemoteOperation; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.utils.FileStorageUtils; + import android.accounts.Account; + -import eu.alefzero.webdav.FileRequestEntity; -import eu.alefzero.webdav.OnDatatransferProgressListener; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; - /** * Remote operation performing the upload of a file to an ownCloud server * diff --combined src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java index 867eb53e,6e815579..74d26868 --- a/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java @@@ -24,12 -24,12 +24,13 @@@ import org.apache.http.HttpRequest import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; -import com.owncloud.android.authentication.AccountUtils; -import com.owncloud.android.authentication.AccountUtils.AccountNotFoundException; import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.network.OwnCloudClientUtils; +import com.owncloud.android.oc_framework.accounts.AccountUtils; +import com.owncloud.android.oc_framework.accounts.AccountUtils.AccountNotFoundException; +import com.owncloud.android.oc_framework.network.webdav.OwnCloudClientFactory; +import com.owncloud.android.oc_framework.network.webdav.WebdavClient; + import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AuthenticatorException; @@@ -37,6 -37,7 +38,6 @@@ import android.accounts.OperationCancel import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; import android.content.Context; -import eu.alefzero.webdav.WebdavClient; /** * Base synchronization adapter for ownCloud designed to be subclassed for different @@@ -52,7 -53,7 +53,7 @@@ public abstract class AbstractOwnCloudS private AccountManager accountManager; private Account account; - private ContentProviderClient contentProvider; + private ContentProviderClient mContentProviderClient; private FileDataStorageManager mStoreManager; private WebdavClient mClient = null; @@@ -62,6 -63,11 +63,11 @@@ this.setAccountManager(AccountManager.get(context)); } + public AbstractOwnCloudSyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) { + super(context, autoInitialize, allowParallelSyncs); + this.setAccountManager(AccountManager.get(context)); + } + public AccountManager getAccountManager() { return accountManager; } @@@ -78,12 -84,12 +84,12 @@@ this.account = account; } - public ContentProviderClient getContentProvider() { - return contentProvider; + public ContentProviderClient getContentProviderClient() { + return mContentProviderClient; } - public void setContentProvider(ContentProviderClient contentProvider) { - this.contentProvider = contentProvider; + public void setContentProviderClient(ContentProviderClient contentProvider) { + this.mContentProviderClient = contentProvider; } public void setStorageManager(FileDataStorageManager storage_manager) { @@@ -96,7 -102,7 +102,7 @@@ protected void initClientForCurrentAccount() throws OperationCanceledException, AuthenticatorException, IOException, AccountNotFoundException { AccountUtils.constructFullURLForAccount(getContext(), account); - mClient = OwnCloudClientUtils.createOwnCloudClient(account, getContext()); + mClient = OwnCloudClientFactory.createOwnCloudClient(account, getContext()); } protected WebdavClient getClient() { diff --combined src/com/owncloud/android/syncadapter/ContactSyncAdapter.java index 5f2b4dd1,662c8a64..9bf5d835 --- a/src/com/owncloud/android/syncadapter/ContactSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/ContactSyncAdapter.java @@@ -26,8 -26,8 +26,9 @@@ import org.apache.http.entity.ByteArray import com.owncloud.android.authentication.AccountAuthenticator; import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.oc_framework.accounts.OwnCloudAccount; + import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AuthenticatorException; @@@ -53,7 -53,7 +54,7 @@@ public class ContactSyncAdapter extend public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { setAccount(account); - setContentProvider(provider); + setContentProviderClient(provider); Cursor c = getLocalContacts(false); if (c.moveToFirst()) { do { @@@ -92,7 -92,7 +93,7 @@@ AccountManager am = getAccountManager(); @SuppressWarnings("deprecation") String uri = am.getUserData(getAccount(), - AccountAuthenticator.KEY_OC_URL).replace( + OwnCloudAccount.Constants.KEY_OC_URL).replace( AccountUtils.WEBDAV_PATH_2_0, AccountUtils.CARDDAV_PATH_2_0); uri += "/addressbooks/" + getAccount().name.substring(0, diff --combined src/com/owncloud/android/syncadapter/FileSyncAdapter.java index 797dbbf8,5b0376c3..b84aa314 --- a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java @@@ -32,12 -32,13 +32,13 @@@ import com.owncloud.android.R import com.owncloud.android.authentication.AuthenticatorActivity; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.oc_framework.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.oc_framework.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.ui.activity.ErrorsWhileCopyingHandlerActivity; + import android.accounts.Account; import android.accounts.AccountsException; import android.app.Notification; @@@ -97,7 -98,7 +98,7 @@@ public class FileSyncAdapter extends Ab /** - * Creates an {@link FileSyncAdapter} + * Creates a {@link FileSyncAdapter} * * {@inheritDoc} */ @@@ -107,11 -108,21 +108,21 @@@ /** + * Creates a {@link FileSyncAdapter} + * + * {@inheritDoc} + */ + public FileSyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) { + super(context, autoInitialize, allowParallelSyncs); + } + + + /** * {@inheritDoc} */ @Override public synchronized void onPerformSync(Account account, Bundle extras, - String authority, ContentProviderClient provider, + String authority, ContentProviderClient providerClient, SyncResult syncResult) { mCancellation = false; @@@ -126,8 -137,8 +137,8 @@@ mSyncResult.delayUntil = 60*60*24; // avoid too many automatic synchronizations this.setAccount(account); - this.setContentProvider(provider); - this.setStorageManager(new FileDataStorageManager(account, provider)); + this.setContentProviderClient(providerClient); + this.setStorageManager(new FileDataStorageManager(account, providerClient)); try { this.initClientForCurrentAccount(); } catch (IOException e) { @@@ -268,9 -279,8 +279,9 @@@ } else { // in failures, the statistics for the global result are updated if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED || - ( result.isIdPRedirection() && - MainApp.getAuthTokenTypeSamlSessionCookie().equals(getClient().getAuthTokenType()))) { + ( result.isIdPRedirection() && + getClient().getCredentials() == null )) { + //MainApp.getAuthTokenTypeSamlSessionCookie().equals(getClient().getAuthTokenType()))) { mSyncResult.stats.numAuthExceptions++; } else if (result.getException() instanceof DavException) { @@@ -361,9 -371,9 +372,9 @@@ notification.flags |= Notification.FLAG_AUTO_CANCEL; boolean needsToUpdateCredentials = (mLastFailedResult != null && ( mLastFailedResult.getCode() == ResultCode.UNAUTHORIZED || - // (mLastFailedResult.isTemporalRedirection() && mLastFailedResult.isIdPRedirection() && ( mLastFailedResult.isIdPRedirection() && - MainApp.getAuthTokenTypeSamlSessionCookie().equals(getClient().getAuthTokenType())) + getClient().getCredentials() == null ) + //MainApp.getAuthTokenTypeSamlSessionCookie().equals(getClient().getAuthTokenType())) ) ); // TODO put something smart in the contentIntent below for all the possible errors diff --combined src/com/owncloud/android/ui/activity/AccountSelectActivity.java index 0a3f437c,716d6dfd..c8f2c429 --- a/src/com/owncloud/android/ui/activity/AccountSelectActivity.java +++ b/src/com/owncloud/android/ui/activity/AccountSelectActivity.java @@@ -47,15 -47,14 +47,14 @@@ import com.actionbarsherlock.app.Sherlo import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; - import com.owncloud.android.authentication.AccountAuthenticator; +import com.owncloud.android.authentication.AuthenticatorActivity; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.oc_framework.accounts.OwnCloudAccount; import com.owncloud.android.Log_OC; import com.owncloud.android.MainApp; - import com.owncloud.android.R; -import com.owncloud.android.authentication.AccountAuthenticator; -import com.owncloud.android.authentication.AccountUtils; -import com.owncloud.android.authentication.AuthenticatorActivity; + public class AccountSelectActivity extends SherlockListActivity implements AccountManagerCallback { @@@ -96,12 -95,6 +95,6 @@@ (mPreviousAccount != null && !mPreviousAccount.equals(current))) { /// the account set as default changed since this activity was created - // trigger synchronization - ContentResolver.cancelSync(null, MainApp.getAuthTokenType()); - Bundle bundle = new Bundle(); - bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - ContentResolver.requestSync(AccountUtils.getCurrentOwnCloudAccount(this), MainApp.getAuthTokenType(), bundle); - // restart the main activity Intent i = new Intent(this, FileDisplayActivity.class); i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); @@@ -218,7 -211,7 +211,7 @@@ h.put("VER", "ownCloud version: " + am.getUserData(a, - AccountAuthenticator.KEY_OC_VERSION)); + OwnCloudAccount.Constants.KEY_OC_VERSION)); ll.add(h); } diff --combined src/com/owncloud/android/ui/activity/FileDisplayActivity.java index 55edf82e,39b7bd8a..43a8c97a --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@@ -31,8 -31,10 +31,10 @@@ import android.content.Context import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; + import android.content.IntentFilter.AuthorityEntry; import android.content.ServiceConnection; import android.content.SharedPreferences; + import android.content.SyncRequest; import android.content.res.Resources.NotFoundException; import android.database.Cursor; import android.net.Uri; @@@ -63,15 -65,15 +65,15 @@@ import com.owncloud.android.R 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.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; import com.owncloud.android.operations.CreateFolderOperation; -import com.owncloud.android.operations.OnRemoteOperationListener; -import com.owncloud.android.operations.RemoteOperation; -import com.owncloud.android.operations.RemoteOperationResult; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.oc_framework.operations.OnRemoteOperationListener; +import com.owncloud.android.oc_framework.operations.RemoteOperation; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.operations.RemoveFileOperation; import com.owncloud.android.operations.RenameFileOperation; import com.owncloud.android.operations.SynchronizeFileOperation; @@@ -89,6 -91,7 +91,7 @@@ import com.owncloud.android.ui.preview. import com.owncloud.android.ui.preview.PreviewMediaFragment; import com.owncloud.android.ui.preview.PreviewVideoActivity; + /** * Displays, what files the user has available in his ownCloud. * @@@ -493,12 -496,27 +496,27 @@@ OCFileListFragment.ContainerActivity, F } private void startSynchronization() { - ContentResolver.cancelSync(null, MainApp.getAuthTokenType()); // cancel the current synchronizations of any ownCloud account - Bundle bundle = new Bundle(); - bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - ContentResolver.requestSync( - getAccount(), - MainApp.getAuthTokenType(), bundle); + Log_OC.e(TAG, "Got to start sync"); + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) { + Log_OC.e(TAG, "Canceling all syncs for " + MainApp.getAuthority()); + ContentResolver.cancelSync(null, MainApp.getAuthority()); // cancel the current synchronizations of any ownCloud account + Bundle bundle = new Bundle(); + bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); + Log_OC.e(TAG, "Requesting sync for " + getAccount().name + " at " + MainApp.getAuthority()); + ContentResolver.requestSync( + getAccount(), + MainApp.getAuthority(), bundle); + } else { + Log_OC.e(TAG, "Requesting sync for " + getAccount().name + " at " + MainApp.getAuthority() + " with new API"); + SyncRequest.Builder builder = new SyncRequest.Builder(); + builder.setSyncAdapter(getAccount(), MainApp.getAuthority()); + builder.setExpedited(true); + builder.setManual(true); + builder.syncOnce(); + SyncRequest request = builder.build(); + ContentResolver.requestSync(request); + } } @@@ -1237,7 -1255,7 +1255,7 @@@ @Override public void onSavedCertificate() { - startSynchronization(); + startSyncFolderOperation(getCurrentDir()); } diff --combined src/com/owncloud/android/ui/dialog/SslValidatorDialog.java index ed5d94e1,1ccc8fa8..dd8fee88 --- a/src/com/owncloud/android/ui/dialog/SslValidatorDialog.java +++ b/src/com/owncloud/android/ui/dialog/SslValidatorDialog.java @@@ -29,6 -29,12 +29,9 @@@ import java.util.Map import javax.security.auth.x500.X500Principal; + import com.owncloud.android.Log_OC; + import com.owncloud.android.R; -import com.owncloud.android.network.CertificateCombinedException; -import com.owncloud.android.network.OwnCloudClientUtils; -import com.owncloud.android.operations.RemoteOperationResult; + import android.app.Dialog; import android.content.Context; import android.os.Bundle; @@@ -37,11 -43,6 +40,9 @@@ import android.view.Window import android.widget.Button; import android.widget.TextView; - import com.owncloud.android.Log_OC; - import com.owncloud.android.R; +import com.owncloud.android.oc_framework.network.CertificateCombinedException; +import com.owncloud.android.oc_framework.network.NetworkUtils; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult; /** * Dialog to request the user about a certificate that could not be validated with the certificates store in the system. @@@ -142,11 -143,11 +143,11 @@@ public class SslValidatorDialog extend View detailsScroll = findViewById(R.id.details_scroll); if (detailsScroll.getVisibility() == View.VISIBLE) { detailsScroll.setVisibility(View.GONE); - ((Button)v).setText(R.string.ssl_validator_btn_details_see); + ((Button) v).setText(R.string.ssl_validator_btn_details_see); } else { detailsScroll.setVisibility(View.VISIBLE); - ((Button)v).setText(R.string.ssl_validator_btn_details_hide); + ((Button) v).setText(R.string.ssl_validator_btn_details_hide); } } }); @@@ -342,7 -343,7 +343,7 @@@ private void saveServerCert() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { if (mException.getServerCertificate() != null) { // TODO make this asynchronously, it can take some time - OwnCloudClientUtils.addCertToKnownServersStore(mException.getServerCertificate(), getContext()); + NetworkUtils.addCertToKnownServersStore(mException.getServerCertificate(), getContext()); } } diff --combined src/com/owncloud/android/ui/fragment/FileDetailFragment.java index 5d7004ee,af737662..9c01ce70 --- a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java @@@ -48,18 -48,17 +48,18 @@@ import com.owncloud.android.Log_OC import com.owncloud.android.R; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; - import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileObserverService; import com.owncloud.android.files.services.FileUploader; + import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; -import com.owncloud.android.operations.OnRemoteOperationListener; -import com.owncloud.android.operations.RemoteOperation; -import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.oc_framework.network.webdav.OnDatatransferProgressListener; +import com.owncloud.android.oc_framework.operations.OnRemoteOperationListener; +import com.owncloud.android.oc_framework.operations.RemoteOperation; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult; +import com.owncloud.android.oc_framework.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.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.ui.activity.ConflictsResolveActivity; import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; @@@ -68,6 -67,8 +68,6 @@@ import com.owncloud.android.ui.dialog.E import com.owncloud.android.ui.preview.PreviewImageFragment; -import eu.alefzero.webdav.OnDatatransferProgressListener; - /** * This Fragment is used to display the details about a file. * diff --combined src/com/owncloud/android/ui/fragment/OCFileListFragment.java index a4832650,a3c6d3be..f1f45fb4 --- a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java @@@ -29,8 -29,8 +29,8 @@@ import com.owncloud.android.datamodel.O import com.owncloud.android.files.FileHandler; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; -import com.owncloud.android.operations.OnRemoteOperationListener; -import com.owncloud.android.operations.RemoteOperation; +import com.owncloud.android.oc_framework.operations.OnRemoteOperationListener; +import com.owncloud.android.oc_framework.operations.RemoteOperation; import com.owncloud.android.operations.RemoveFileOperation; import com.owncloud.android.operations.RenameFileOperation; import com.owncloud.android.operations.SynchronizeFileOperation; @@@ -43,6 -43,7 +43,7 @@@ import com.owncloud.android.ui.fragment import com.owncloud.android.ui.preview.PreviewImageFragment; import com.owncloud.android.ui.preview.PreviewMediaFragment; + import android.accounts.Account; import android.app.Activity; import android.os.Bundle; diff --combined src/com/owncloud/android/ui/preview/FileDownloadFragment.java index 1e73dbe5,306df1f7..c6f5ff4f --- a/src/com/owncloud/android/ui/preview/FileDownloadFragment.java +++ b/src/com/owncloud/android/ui/preview/FileDownloadFragment.java @@@ -19,6 -19,12 +19,12 @@@ package com.owncloud.android.ui.preview import java.lang.ref.WeakReference; + import com.owncloud.android.Log_OC; + import com.owncloud.android.R; + import com.owncloud.android.datamodel.OCFile; + import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; + import com.owncloud.android.ui.fragment.FileFragment; + import android.accounts.Account; import android.app.Activity; import android.os.Bundle; @@@ -31,15 -37,10 +37,9 @@@ import android.widget.ImageButton import android.widget.ProgressBar; import android.widget.TextView; - import com.owncloud.android.datamodel.OCFile; - import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; +import com.owncloud.android.oc_framework.network.webdav.OnDatatransferProgressListener; - 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. * diff --combined src/com/owncloud/android/ui/preview/PreviewImageFragment.java index 9a3eee1e,9ef4db4b..7ad57ed0 --- a/src/com/owncloud/android/ui/preview/PreviewImageFragment.java +++ b/src/com/owncloud/android/ui/preview/PreviewImageFragment.java @@@ -50,19 -50,19 +50,18 @@@ import android.widget.Toast import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; + import com.owncloud.android.Log_OC; + import com.owncloud.android.R; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.operations.OnRemoteOperationListener; -import com.owncloud.android.operations.RemoteOperation; -import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.oc_framework.network.webdav.WebdavUtils; +import com.owncloud.android.oc_framework.operations.OnRemoteOperationListener; +import com.owncloud.android.oc_framework.operations.RemoteOperation; +import com.owncloud.android.oc_framework.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.WebdavUtils; -- /** * This fragment shows a preview of a downloaded image. diff --combined src/com/owncloud/android/ui/preview/PreviewMediaFragment.java index 70250b99,815dbbde..78c75f8f --- a/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java +++ b/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java @@@ -52,23 -52,23 +52,23 @@@ import android.widget.VideoView import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; + import com.owncloud.android.Log_OC; + import com.owncloud.android.R; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.media.MediaControlView; import com.owncloud.android.media.MediaService; import com.owncloud.android.media.MediaServiceBinder; -import com.owncloud.android.operations.OnRemoteOperationListener; -import com.owncloud.android.operations.RemoteOperation; -import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.oc_framework.network.webdav.WebdavUtils; +import com.owncloud.android.oc_framework.operations.OnRemoteOperationListener; +import com.owncloud.android.oc_framework.operations.RemoteOperation; +import com.owncloud.android.oc_framework.operations.RemoteOperationResult; import com.owncloud.android.operations.RemoveFileOperation; import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.fragment.ConfirmationDialogFragment; import com.owncloud.android.ui.fragment.FileFragment; - import com.owncloud.android.Log_OC; - import com.owncloud.android.R; -import eu.alefzero.webdav.WebdavUtils; /** * This fragment shows a preview of a downloaded media file (audio or video). diff --combined src/com/owncloud/android/ui/preview/PreviewVideoActivity.java index 2c90afb9,7db07b35..1e0d307a --- a/src/com/owncloud/android/ui/preview/PreviewVideoActivity.java +++ b/src/com/owncloud/android/ui/preview/PreviewVideoActivity.java @@@ -17,6 -17,15 +17,13 @@@ package com.owncloud.android.ui.preview; + import com.owncloud.android.Log_OC; + import com.owncloud.android.R; -import com.owncloud.android.authentication.AccountUtils; -import com.owncloud.android.authentication.AccountUtils.AccountNotFoundException; + import com.owncloud.android.datamodel.FileDataStorageManager; + import com.owncloud.android.datamodel.OCFile; + import com.owncloud.android.media.MediaService; + import com.owncloud.android.ui.activity.FileActivity; + import android.accounts.Account; import android.app.AlertDialog; import android.content.DialogInterface; @@@ -30,15 -39,6 +37,9 @@@ import android.os.Bundle import android.widget.MediaController; import android.widget.VideoView; - import com.owncloud.android.Log_OC; - import com.owncloud.android.R; - import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.oc_framework.accounts.AccountUtils; +import com.owncloud.android.oc_framework.accounts.AccountUtils.AccountNotFoundException; - import com.owncloud.android.datamodel.OCFile; - import com.owncloud.android.media.MediaService; - import com.owncloud.android.ui.activity.FileActivity; + /** * Activity implementing a basic video player. * diff --combined src/com/owncloud/android/utils/FileStorageUtils.java index e221de5f,0c7e9911..25206443 --- a/src/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@@ -19,15 -19,16 +19,16 @@@ package com.owncloud.android.utils import java.io.File; + import com.owncloud.android.MainApp; + import com.owncloud.android.R; + import com.owncloud.android.datamodel.OCFile; + import android.annotation.SuppressLint; import android.content.Context; import android.net.Uri; import android.os.Environment; import android.os.StatFs; - import com.owncloud.android.MainApp; - import com.owncloud.android.R; - import com.owncloud.android.datamodel.OCFile; /** * Static methods to help in access to local file system. @@@ -75,11 -76,5 +76,11 @@@ public class FileStorageUtils String value = uploadPath + OCFile.PATH_SEPARATOR + (fileName == null ? "" : fileName); return value; } + + public static String getParentPath(String remotePath) { + String parentPath = new File(remotePath).getParent(); + parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR; + return parentPath; + } } diff --combined tests/.classpath index 3ac9c4d6,66527241..74f453aa --- a/tests/.classpath +++ b/tests/.classpath @@@ -1,11 -1,10 +1,4 @@@ - - -- -- -- -- - - -