Loading build.gradle +1 −1 Original line number Diff line number Diff line Loading @@ -144,7 +144,7 @@ dependencies { implementation 'commons-io:commons-io:2.6' implementation 'com.squareup.retrofit2:retrofit:2.6.2' implementation 'com.squareup.retrofit2:retrofit:2.7.1' implementation 'com.squareup.okhttp3:okhttp:3.12.8' // 3.13+ requires Lollipop, but our minSdkVersion is 14 spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.10.1' Loading src/main/java/com/nextcloud/android/sso/aidl/NextcloudRequest.java +33 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ package com.nextcloud.android.sso.aidl; import androidx.core.util.ObjectsCompat; import java.io.InputStream; import java.io.Serializable; import java.util.HashMap; import java.util.List; Loading @@ -41,10 +42,25 @@ public class NextcloudRequest implements Serializable { private String token; private String packageName; private String accountName; private transient InputStream bodyAsStream = null; private boolean followRedirects; private NextcloudRequest() { } public NextcloudRequest(NextcloudRequest ncr) { this.method = ncr.method; this.requestBody = ncr.requestBody; this.url = ncr.url; this.token = ncr.token; this.packageName = ncr.packageName; this.accountName = ncr.accountName; this.followRedirects = ncr.followRedirects; header = new HashMap<>(ncr.header); parameter = new HashMap<>(ncr.parameter); bodyAsStream = ncr.bodyAsStream; } public static class Builder implements Serializable { private static final long serialVersionUID = 2121321432424242L; //assign a long value Loading @@ -55,6 +71,10 @@ public class NextcloudRequest implements Serializable { ncr = new NextcloudRequest(); } public Builder(Builder cloneSource) { ncr = new NextcloudRequest(cloneSource.ncr); } public NextcloudRequest build() { return ncr; } Loading @@ -78,6 +98,10 @@ public class NextcloudRequest implements Serializable { ncr.requestBody = requestBody; return this; } public Builder setRequestBodyAsStream(InputStream requestBody) { ncr.bodyAsStream = requestBody; return this; } public Builder setUrl(String url) { ncr.url = url; Loading @@ -94,6 +118,8 @@ public class NextcloudRequest implements Serializable { return this; } /** * Default value: true * @param followRedirects Loading Loading @@ -153,6 +179,13 @@ public class NextcloudRequest implements Serializable { return this.followRedirects; } public InputStream getBodyAsStream() { return bodyAsStream; } public void setBodyAsStream(InputStream bodyAsStream) { this.bodyAsStream = bodyAsStream; } @Override public boolean equals(Object obj) { Loading src/main/java/com/nextcloud/android/sso/api/AidlNetworkRequest.java +4 −14 Original line number Diff line number Diff line Loading @@ -11,6 +11,8 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; import androidx.annotation.NonNull; import com.google.gson.Gson; import com.google.gson.internal.LinkedTreeMap; import com.nextcloud.android.sso.Constants; Loading @@ -31,8 +33,6 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; import androidx.annotation.NonNull; import static com.nextcloud.android.sso.exceptions.SSOException.parseNextcloudCustomException; public class AidlNetworkRequest extends NetworkRequest { Loading Loading @@ -219,22 +219,12 @@ public class AidlNetworkRequest extends NetworkRequest { InputStream is = new ByteArrayInputStream(baos.toByteArray()); ParcelFileDescriptor input = ParcelFileDescriptorUtil.pipeFrom(is, new IThreadListener() { @Override public void onThreadFinished(Thread thread) { Log.d(TAG, "copy data from service finished"); } }); thread -> Log.d(TAG, "copy data from service finished")); ParcelFileDescriptor requestBodyParcelFileDescriptor = null; if(requestBodyInputStream != null) { requestBodyParcelFileDescriptor = ParcelFileDescriptorUtil.pipeFrom(requestBodyInputStream, new IThreadListener() { @Override public void onThreadFinished(Thread thread) { Log.d(TAG, "copy data from service finished"); } }); thread -> Log.d(TAG, "copy data from service finished")); } ParcelFileDescriptor output; Loading src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java +2 −2 Original line number Diff line number Diff line Loading @@ -116,11 +116,11 @@ public class NextcloudAPI { * @throws Exception or SSOException */ public InputStream performNetworkRequest(NextcloudRequest request) throws Exception { return networkRequest.performNetworkRequest(request, null); return networkRequest.performNetworkRequest(request, request.getBodyAsStream()); } public Response performRequestV2(final @NonNull Type type, NextcloudRequest request) throws Exception { Log.d(TAG, "performRequest() called with: type = [" + type + "], request = [" + request + "]"); Log.d(TAG, "performRequestV2() called with: type = [" + type + "], request = [" + request + "]"); Response result = null; Response response = performNetworkRequestV2(request); Loading src/main/java/com/nextcloud/android/sso/api/NextcloudRetrofitServiceMethod.java +73 −34 Original line number Diff line number Diff line Loading @@ -2,18 +2,16 @@ package com.nextcloud.android.sso.api; import android.util.Log; import androidx.annotation.Nullable; import com.nextcloud.android.sso.aidl.NextcloudRequest; import com.nextcloud.android.sso.helper.Okhttp3Helper; import com.nextcloud.android.sso.helper.ReactivexHelper; import com.nextcloud.android.sso.helper.Retrofit2Helper; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; Loading @@ -27,12 +25,14 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import androidx.annotation.Nullable; import io.reactivex.Completable; import io.reactivex.Observable; import okhttp3.Headers; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.RequestBody; import okhttp3.ResponseBody; import okio.Buffer; import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.DELETE; Loading @@ -40,9 +40,13 @@ import retrofit2.http.Field; import retrofit2.http.FieldMap; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.HEAD; import retrofit2.http.HTTP; import retrofit2.http.Header; import retrofit2.http.Multipart; import retrofit2.http.POST; import retrofit2.http.PUT; import retrofit2.http.Part; import retrofit2.http.Path; import retrofit2.http.Query; import retrofit2.http.Streaming; Loading @@ -66,6 +70,8 @@ public class NextcloudRetrofitServiceMethod<T> { private Map<String, String> queryParameters; private final NextcloudRequest.Builder requestBuilder; private boolean isMultipart = false; private boolean isFormEncoded = false; public NextcloudRetrofitServiceMethod(String apiEndpoint, Method method) { Loading Loading @@ -100,7 +106,7 @@ public class NextcloudRetrofitServiceMethod<T> { throw new InvalidParameterException("Expected: " + parameterAnnotationsArray.length + " params - were: " + args.length); } NextcloudRequest.Builder rBuilder = cloneSerializable(requestBuilder); NextcloudRequest.Builder rBuilder = new NextcloudRequest.Builder(requestBuilder); Map<String, String> parameters = new HashMap<>(); Loading @@ -108,10 +114,14 @@ public class NextcloudRetrofitServiceMethod<T> { // Copy all static query params into parameters array parameters.putAll(this.queryParameters); MultipartBody.Builder multipartBuilder = null; if (isMultipart) { multipartBuilder = new MultipartBody.Builder(); } // Build/parse dynamic parameters for(int i = 0; i < parameterAnnotationsArray.length; i++) { Annotation annotation = parameterAnnotationsArray[i][0]; if(annotation instanceof Query) { parameters.put(((Query)annotation).value(), String.valueOf(args[i])); } else if(annotation instanceof Body) { Loading @@ -121,13 +131,9 @@ public class NextcloudRetrofitServiceMethod<T> { String url = rBuilder.build().getUrl(); rBuilder.setUrl(url.replace(varName, String.valueOf(args[i]))); } else if(annotation instanceof Header) { Map<String, List<String>> headers = rBuilder.build().getHeader(); List<String> arg = new ArrayList<>(); if(args[i] != null) { arg.add(String.valueOf(args[i])); headers.put(((Header) annotation).value(), arg); } rBuilder.setHeader(headers); Object value = args[i]; String key =((Header) annotation).value(); addHeader(rBuilder, key, value); } else if(annotation instanceof FieldMap) { if(args[i] != null) { Map<String, Object> fieldMap = (HashMap<String, Object>) args[i]; Loading @@ -140,11 +146,24 @@ public class NextcloudRetrofitServiceMethod<T> { String field = args[i].toString(); parameters.put(((Field)annotation).value(), field); } } else if(annotation instanceof Part) { if (args[i] instanceof MultipartBody.Part){ multipartBuilder.addPart((MultipartBody.Part) args[i]); } else { throw new IllegalArgumentException("Only MultipartBody.Part type is supported as a @Part"); } } else { throw new UnsupportedOperationException("don't know this type yet.. [" + annotation + "]"); } } // include multipart body as stream, set header if (isMultipart) { MultipartBody multipartBody = multipartBuilder.build(); addHeader(rBuilder, "Content-Type", MultipartBody.FORM+"; boundary="+multipartBody.boundary()); rBuilder.setRequestBodyAsStream(bodyToStream(multipartBody)); } NextcloudRequest request = rBuilder .setParameter(parameters) .build(); Loading Loading @@ -176,6 +195,30 @@ public class NextcloudRetrofitServiceMethod<T> { return nextcloudAPI.performRequest(this.returnType, request); } private void addHeader(NextcloudRequest.Builder rBuilder, String key, Object value) { if (key == null || value == null) { Log.d(TAG, "WARNING: Header not set - key or value missing! Key: " + key + " | Value: " + value); return; } Map<String, List<String>> headers = rBuilder.build().getHeader(); List<String> arg = new ArrayList<>(); arg.add(String.valueOf(value)); headers.put(key, arg); rBuilder.setHeader(headers); } private static InputStream bodyToStream(final RequestBody request){ try { final RequestBody copy = request; final Buffer buffer = new Buffer(); copy.writeTo(buffer); return buffer.inputStream(); } catch (final IOException e) { throw new IllegalStateException("failed to build request-body", e); } } private void parseMethodAnnotation(Annotation annotation) { if (annotation instanceof DELETE) { parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false); Loading @@ -185,6 +228,21 @@ public class NextcloudRetrofitServiceMethod<T> { parseHttpMethodAndPath("POST", ((POST) annotation).value(), true); } else if (annotation instanceof PUT) { parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true); } else if (annotation instanceof HEAD) { parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false); } else if (annotation instanceof HTTP) { HTTP http = (HTTP) annotation; parseHttpMethodAndPath(http.method(), http.path(), http.hasBody()); } else if (annotation instanceof Multipart) { if (isFormEncoded) { throw methodError(method, "Only one encoding annotation is allowed."); } isMultipart = true; } else if (annotation instanceof FormUrlEncoded) { if (isMultipart) { throw methodError(method, "Only one encoding annotation is allowed."); } isFormEncoded = true; } else if (annotation instanceof Streaming) { Log.v(TAG, "streaming interface"); } else if (annotation instanceof retrofit2.http.Headers) { Loading Loading @@ -289,23 +347,4 @@ public class NextcloudRetrofitServiceMethod<T> { + "." + method.getName(), cause); } private static <T extends Serializable> T cloneSerializable(T o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream( baos ); oos.writeObject(o); oos.close(); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()) ); T res = null; try { res = (T) ois.readObject(); } catch (ClassNotFoundException e) { // Can't happen as we just clone an object.. Log.e(TAG, "ClassNotFoundException", e); } ois.close(); return res; } } Loading
build.gradle +1 −1 Original line number Diff line number Diff line Loading @@ -144,7 +144,7 @@ dependencies { implementation 'commons-io:commons-io:2.6' implementation 'com.squareup.retrofit2:retrofit:2.6.2' implementation 'com.squareup.retrofit2:retrofit:2.7.1' implementation 'com.squareup.okhttp3:okhttp:3.12.8' // 3.13+ requires Lollipop, but our minSdkVersion is 14 spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.10.1' Loading
src/main/java/com/nextcloud/android/sso/aidl/NextcloudRequest.java +33 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ package com.nextcloud.android.sso.aidl; import androidx.core.util.ObjectsCompat; import java.io.InputStream; import java.io.Serializable; import java.util.HashMap; import java.util.List; Loading @@ -41,10 +42,25 @@ public class NextcloudRequest implements Serializable { private String token; private String packageName; private String accountName; private transient InputStream bodyAsStream = null; private boolean followRedirects; private NextcloudRequest() { } public NextcloudRequest(NextcloudRequest ncr) { this.method = ncr.method; this.requestBody = ncr.requestBody; this.url = ncr.url; this.token = ncr.token; this.packageName = ncr.packageName; this.accountName = ncr.accountName; this.followRedirects = ncr.followRedirects; header = new HashMap<>(ncr.header); parameter = new HashMap<>(ncr.parameter); bodyAsStream = ncr.bodyAsStream; } public static class Builder implements Serializable { private static final long serialVersionUID = 2121321432424242L; //assign a long value Loading @@ -55,6 +71,10 @@ public class NextcloudRequest implements Serializable { ncr = new NextcloudRequest(); } public Builder(Builder cloneSource) { ncr = new NextcloudRequest(cloneSource.ncr); } public NextcloudRequest build() { return ncr; } Loading @@ -78,6 +98,10 @@ public class NextcloudRequest implements Serializable { ncr.requestBody = requestBody; return this; } public Builder setRequestBodyAsStream(InputStream requestBody) { ncr.bodyAsStream = requestBody; return this; } public Builder setUrl(String url) { ncr.url = url; Loading @@ -94,6 +118,8 @@ public class NextcloudRequest implements Serializable { return this; } /** * Default value: true * @param followRedirects Loading Loading @@ -153,6 +179,13 @@ public class NextcloudRequest implements Serializable { return this.followRedirects; } public InputStream getBodyAsStream() { return bodyAsStream; } public void setBodyAsStream(InputStream bodyAsStream) { this.bodyAsStream = bodyAsStream; } @Override public boolean equals(Object obj) { Loading
src/main/java/com/nextcloud/android/sso/api/AidlNetworkRequest.java +4 −14 Original line number Diff line number Diff line Loading @@ -11,6 +11,8 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; import androidx.annotation.NonNull; import com.google.gson.Gson; import com.google.gson.internal.LinkedTreeMap; import com.nextcloud.android.sso.Constants; Loading @@ -31,8 +33,6 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; import androidx.annotation.NonNull; import static com.nextcloud.android.sso.exceptions.SSOException.parseNextcloudCustomException; public class AidlNetworkRequest extends NetworkRequest { Loading Loading @@ -219,22 +219,12 @@ public class AidlNetworkRequest extends NetworkRequest { InputStream is = new ByteArrayInputStream(baos.toByteArray()); ParcelFileDescriptor input = ParcelFileDescriptorUtil.pipeFrom(is, new IThreadListener() { @Override public void onThreadFinished(Thread thread) { Log.d(TAG, "copy data from service finished"); } }); thread -> Log.d(TAG, "copy data from service finished")); ParcelFileDescriptor requestBodyParcelFileDescriptor = null; if(requestBodyInputStream != null) { requestBodyParcelFileDescriptor = ParcelFileDescriptorUtil.pipeFrom(requestBodyInputStream, new IThreadListener() { @Override public void onThreadFinished(Thread thread) { Log.d(TAG, "copy data from service finished"); } }); thread -> Log.d(TAG, "copy data from service finished")); } ParcelFileDescriptor output; Loading
src/main/java/com/nextcloud/android/sso/api/NextcloudAPI.java +2 −2 Original line number Diff line number Diff line Loading @@ -116,11 +116,11 @@ public class NextcloudAPI { * @throws Exception or SSOException */ public InputStream performNetworkRequest(NextcloudRequest request) throws Exception { return networkRequest.performNetworkRequest(request, null); return networkRequest.performNetworkRequest(request, request.getBodyAsStream()); } public Response performRequestV2(final @NonNull Type type, NextcloudRequest request) throws Exception { Log.d(TAG, "performRequest() called with: type = [" + type + "], request = [" + request + "]"); Log.d(TAG, "performRequestV2() called with: type = [" + type + "], request = [" + request + "]"); Response result = null; Response response = performNetworkRequestV2(request); Loading
src/main/java/com/nextcloud/android/sso/api/NextcloudRetrofitServiceMethod.java +73 −34 Original line number Diff line number Diff line Loading @@ -2,18 +2,16 @@ package com.nextcloud.android.sso.api; import android.util.Log; import androidx.annotation.Nullable; import com.nextcloud.android.sso.aidl.NextcloudRequest; import com.nextcloud.android.sso.helper.Okhttp3Helper; import com.nextcloud.android.sso.helper.ReactivexHelper; import com.nextcloud.android.sso.helper.Retrofit2Helper; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; Loading @@ -27,12 +25,14 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import androidx.annotation.Nullable; import io.reactivex.Completable; import io.reactivex.Observable; import okhttp3.Headers; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.RequestBody; import okhttp3.ResponseBody; import okio.Buffer; import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.DELETE; Loading @@ -40,9 +40,13 @@ import retrofit2.http.Field; import retrofit2.http.FieldMap; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.HEAD; import retrofit2.http.HTTP; import retrofit2.http.Header; import retrofit2.http.Multipart; import retrofit2.http.POST; import retrofit2.http.PUT; import retrofit2.http.Part; import retrofit2.http.Path; import retrofit2.http.Query; import retrofit2.http.Streaming; Loading @@ -66,6 +70,8 @@ public class NextcloudRetrofitServiceMethod<T> { private Map<String, String> queryParameters; private final NextcloudRequest.Builder requestBuilder; private boolean isMultipart = false; private boolean isFormEncoded = false; public NextcloudRetrofitServiceMethod(String apiEndpoint, Method method) { Loading Loading @@ -100,7 +106,7 @@ public class NextcloudRetrofitServiceMethod<T> { throw new InvalidParameterException("Expected: " + parameterAnnotationsArray.length + " params - were: " + args.length); } NextcloudRequest.Builder rBuilder = cloneSerializable(requestBuilder); NextcloudRequest.Builder rBuilder = new NextcloudRequest.Builder(requestBuilder); Map<String, String> parameters = new HashMap<>(); Loading @@ -108,10 +114,14 @@ public class NextcloudRetrofitServiceMethod<T> { // Copy all static query params into parameters array parameters.putAll(this.queryParameters); MultipartBody.Builder multipartBuilder = null; if (isMultipart) { multipartBuilder = new MultipartBody.Builder(); } // Build/parse dynamic parameters for(int i = 0; i < parameterAnnotationsArray.length; i++) { Annotation annotation = parameterAnnotationsArray[i][0]; if(annotation instanceof Query) { parameters.put(((Query)annotation).value(), String.valueOf(args[i])); } else if(annotation instanceof Body) { Loading @@ -121,13 +131,9 @@ public class NextcloudRetrofitServiceMethod<T> { String url = rBuilder.build().getUrl(); rBuilder.setUrl(url.replace(varName, String.valueOf(args[i]))); } else if(annotation instanceof Header) { Map<String, List<String>> headers = rBuilder.build().getHeader(); List<String> arg = new ArrayList<>(); if(args[i] != null) { arg.add(String.valueOf(args[i])); headers.put(((Header) annotation).value(), arg); } rBuilder.setHeader(headers); Object value = args[i]; String key =((Header) annotation).value(); addHeader(rBuilder, key, value); } else if(annotation instanceof FieldMap) { if(args[i] != null) { Map<String, Object> fieldMap = (HashMap<String, Object>) args[i]; Loading @@ -140,11 +146,24 @@ public class NextcloudRetrofitServiceMethod<T> { String field = args[i].toString(); parameters.put(((Field)annotation).value(), field); } } else if(annotation instanceof Part) { if (args[i] instanceof MultipartBody.Part){ multipartBuilder.addPart((MultipartBody.Part) args[i]); } else { throw new IllegalArgumentException("Only MultipartBody.Part type is supported as a @Part"); } } else { throw new UnsupportedOperationException("don't know this type yet.. [" + annotation + "]"); } } // include multipart body as stream, set header if (isMultipart) { MultipartBody multipartBody = multipartBuilder.build(); addHeader(rBuilder, "Content-Type", MultipartBody.FORM+"; boundary="+multipartBody.boundary()); rBuilder.setRequestBodyAsStream(bodyToStream(multipartBody)); } NextcloudRequest request = rBuilder .setParameter(parameters) .build(); Loading Loading @@ -176,6 +195,30 @@ public class NextcloudRetrofitServiceMethod<T> { return nextcloudAPI.performRequest(this.returnType, request); } private void addHeader(NextcloudRequest.Builder rBuilder, String key, Object value) { if (key == null || value == null) { Log.d(TAG, "WARNING: Header not set - key or value missing! Key: " + key + " | Value: " + value); return; } Map<String, List<String>> headers = rBuilder.build().getHeader(); List<String> arg = new ArrayList<>(); arg.add(String.valueOf(value)); headers.put(key, arg); rBuilder.setHeader(headers); } private static InputStream bodyToStream(final RequestBody request){ try { final RequestBody copy = request; final Buffer buffer = new Buffer(); copy.writeTo(buffer); return buffer.inputStream(); } catch (final IOException e) { throw new IllegalStateException("failed to build request-body", e); } } private void parseMethodAnnotation(Annotation annotation) { if (annotation instanceof DELETE) { parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false); Loading @@ -185,6 +228,21 @@ public class NextcloudRetrofitServiceMethod<T> { parseHttpMethodAndPath("POST", ((POST) annotation).value(), true); } else if (annotation instanceof PUT) { parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true); } else if (annotation instanceof HEAD) { parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false); } else if (annotation instanceof HTTP) { HTTP http = (HTTP) annotation; parseHttpMethodAndPath(http.method(), http.path(), http.hasBody()); } else if (annotation instanceof Multipart) { if (isFormEncoded) { throw methodError(method, "Only one encoding annotation is allowed."); } isMultipart = true; } else if (annotation instanceof FormUrlEncoded) { if (isMultipart) { throw methodError(method, "Only one encoding annotation is allowed."); } isFormEncoded = true; } else if (annotation instanceof Streaming) { Log.v(TAG, "streaming interface"); } else if (annotation instanceof retrofit2.http.Headers) { Loading Loading @@ -289,23 +347,4 @@ public class NextcloudRetrofitServiceMethod<T> { + "." + method.getName(), cause); } private static <T extends Serializable> T cloneSerializable(T o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream( baos ); oos.writeObject(o); oos.close(); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()) ); T res = null; try { res = (T) ois.readObject(); } catch (ClassNotFoundException e) { // Can't happen as we just clone an object.. Log.e(TAG, "ClassNotFoundException", e); } ois.close(); return res; } }