Преглед изворни кода

Implement sso prototype by @David-Developement

David Luhmer пре 7 година
родитељ
комит
bdfabd70e9

+ 10 - 0
src/main/AndroidManifest.xml

@@ -65,6 +65,7 @@
     <uses-permission android:name="android.permission.USE_FINGERPRINT" />
     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
 
+    <permission android:name="com.owncloud.android.sso" android:protectionLevel="signature" />
 
     <application
         android:name=".MainApp"
@@ -289,6 +290,15 @@
         <activity android:name=".ui.activity.ManageSpaceActivity"
                   android:label="@string/manage_space_title"
                   android:theme="@style/Theme.ownCloud" />
+
+
+        <service
+            android:name=".services.AccountManagerService"
+            android:enabled="true"
+            android:exported="true"
+            android:permission="com.owncloud.android.sso" >
+        </service>
+
     </application>
 
 </manifest>

+ 25 - 0
src/main/aidl/de/luhmer/owncloud/accountimporter/helper/IInputStreamService.aidl

@@ -0,0 +1,25 @@
+/***
+	Copyright (c) 2008-2011 CommonsWare, LLC
+	Licensed under the Apache License, Version 2.0 (the "License"); you may not
+	use this file except in compliance with the License. You may obtain	a copy
+	of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
+	by applicable law or agreed to in writing, software distributed under the
+	License is distributed on an "AS IS" BASIS,	WITHOUT	WARRANTIES OR CONDITIONS
+	OF ANY KIND, either express or implied. See the License for the specific
+	language governing permissions and limitations under the License.
+
+	From _The Busy Coder's Guide to Advanced Android Development_
+		http://commonsware.com/AdvAndroid
+
+
+	More information here: https://github.com/abeluck/android-streams-ipc
+*/
+
+package de.luhmer.owncloud.accountimporter.helper;
+
+// Declare the interface.
+interface IInputStreamService {
+
+    ParcelFileDescriptor performNextcloudRequest(in ParcelFileDescriptor input);
+
+}

+ 20 - 0
src/main/java/com/owncloud/android/authentication/AccountAuthenticator.java

@@ -157,6 +157,26 @@ public class AccountAuthenticator extends AbstractAccountAuthenticator {
     public Bundle getAuthToken(AccountAuthenticatorResponse response,
             Account account, String authTokenType, Bundle options)
             throws NetworkErrorException {
+
+        if(authTokenType.equals("NextcloudSSO")) {
+            AccountManager am = AccountManager.get(mContext);
+            final Bundle result = new Bundle();
+
+            String username = account.name.split("@")[0];
+            String server   = account.name.split("@")[1];
+
+            result.putString(AccountManager.KEY_ACCOUNT_NAME,  account.name);
+            result.putString(AccountManager.KEY_ACCOUNT_TYPE,  MainApp.getAccountType());
+            result.putString(AccountManager.KEY_AUTHTOKEN,     "NextcloudSSO");
+            result.putString("username",                       username);
+            result.putString("password",                       am.getPassword(account));
+            result.putString("server_url",                     "https://" + server);
+            result.putBoolean("disable_hostname_verification", false);
+
+            return result;
+        }
+
+
         /// validate parameters
         try {
             validateAccountType(account.type);

+ 278 - 0
src/main/java/com/owncloud/android/services/AccountManagerService.java

@@ -0,0 +1,278 @@
+package com.owncloud.android.services;
+
+import android.accounts.Account;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
+import com.owncloud.android.ui.activity.FileDisplayActivity;
+import com.owncloud.android.ui.asynctasks.AsyncTaskHelper;
+
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.methods.DeleteMethod;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.methods.PutMethod;
+import org.apache.commons.httpclient.methods.StringRequestEntity;
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import de.luhmer.owncloud.accountimporter.helper.InputStreamBinder;
+import de.luhmer.owncloud.accountimporter.helper.NextcloudRequest;
+
+public class AccountManagerService extends Service {
+
+    private static final String TAG = AccountManagerService.class.getCanonicalName();
+
+    public AccountManagerService() { }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return (new InputStreamBinder(this));
+    }
+
+    /** Command to the service to display a message */
+    private static final int MSG_CREATE_NEW_ACCOUNT = 3;
+
+    private static final int MSG_REQUEST_NETWORK_REQUEST = 4;
+    private static final int MSG_RESPONSE_NETWORK_REQUEST = 5;
+
+    /**
+     * Handler of incoming messages from clients.
+     */
+    class IncomingHandler extends Handler {
+        @Override
+        public void handleMessage(final Message msg) {
+            switch (msg.what) {
+                case MSG_CREATE_NEW_ACCOUNT:
+                    addNewAccount();
+                    break;
+                case MSG_REQUEST_NETWORK_REQUEST:
+                    // TODO token check here!
+                    // Validate a Token here! - Make sure, that the other app has the permission to use this functionality
+                    String expectedToken = "test"; // Use the android account manager to get/verify token
+                    final String accountName = msg.getData().getString("account");  // e.g. david@nextcloud.test.de
+                    String token             = msg.getData().getString("token");    // token that the other app received by calling the AccountManager
+                    final boolean stream     = msg.getData().getBoolean("stream");  // Do you want to stream the result?
+                    final Integer port       = msg.getData().getInt("port");
+                    final NextcloudRequest request = (NextcloudRequest) msg.getData().getSerializable("request");
+
+                    if(token.equals(expectedToken)) {
+
+                        Object result = null;
+                        Exception exception = null;
+                        try {
+                            result = AsyncTaskHelper.ExecuteBlockingRequest(new Callable<Object>() {
+                                @Override
+                                public Object call() throws Exception {
+                                    Account account = AccountUtils.getOwnCloudAccountByName(AccountManagerService.this, accountName); // TODO handle case that account is not found!
+                                    OwnCloudAccount ocAccount = new OwnCloudAccount(account, AccountManagerService.this);
+                                    OwnCloudClient client = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, AccountManagerService.this);
+
+                                    //OwnCloudVersion version = AccountUtils.getServerVersion(account);
+                                    //client.setOwnCloudVersion(version);
+
+                                    // TODO do some checks if url is correct!! (prevent ../ in url etc..
+                                    request.url = client.getBaseUri() + request.url;
+
+                                    INetworkInterface network = (stream) ? new StreamingRequest(port) : new PlainRequest();
+
+                                    switch(request.method) {
+                                        case "GET":
+                                            GetMethod get = new GetMethod(request.url);
+                                            get.setQueryString(convertMapToNVP(request.parameter));
+                                            get.addRequestHeader("OCS-APIREQUEST", "true");
+                                            int status = client.executeMethod(get);
+                                            if(status == 200) {
+                                                return network.handleGetRequest(get);
+                                            } else {
+                                                throw new Exception("Network error!!");
+                                            }
+                                        case "POST":
+                                            PostMethod post = new PostMethod(request.url);
+                                            post.setQueryString(convertMapToNVP(request.parameter));
+                                            post.addRequestHeader("OCS-APIREQUEST", "true");
+
+                                            if(request.requestBody != null) {
+                                                StringRequestEntity requestEntity = new StringRequestEntity(
+                                                        request.requestBody,
+                                                        "application/json",
+                                                        "UTF-8");
+                                                post.setRequestEntity(requestEntity);
+                                            }
+                                            int status2 = client.executeMethod(post);
+                                            if(status2 == 200) {
+                                                return network.handlePostRequest(post);
+                                            } else {
+                                                throw new Exception("Network error!!");
+                                            }
+                                        case "PUT":
+                                            PutMethod put = new PutMethod(request.url);
+                                            put.setQueryString(convertMapToNVP(request.parameter));
+                                            put.addRequestHeader("OCS-APIREQUEST", "true");
+
+                                            if(request.requestBody != null) {
+                                                StringRequestEntity requestEntity = new StringRequestEntity(
+                                                        request.requestBody,
+                                                        "application/json",
+                                                        "UTF-8");
+                                                put.setRequestEntity(requestEntity);
+                                            }
+                                            int status4 = client.executeMethod(put);
+                                            if(status4 == 200) {
+                                                return network.handlePutRequest(put);
+                                            } else {
+                                                throw new Exception("Network error!!");
+                                            }
+
+                                        case "DELETE":
+                                            DeleteMethod delete = new DeleteMethod(request.url);
+                                            delete.setQueryString(convertMapToNVP(request.parameter));
+                                            delete.addRequestHeader("OCS-APIREQUEST", "true");
+                                            int status3 = client.executeMethod(delete);
+                                            if(status3 == 200) {
+                                                return true;
+                                            } else {
+                                                throw new Exception("Network error!!");
+                                            }
+                                        default:
+                                            throw new Exception("Unexpected type!!");
+                                    }
+                                }
+                            });
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                            exception = e;
+                        }
+
+                        try {
+                            Message resp = Message.obtain(null, MSG_RESPONSE_NETWORK_REQUEST);
+                            Bundle bResp = new Bundle();
+                            if(!stream) {
+                                bResp.putByteArray("result", (byte[]) result);
+                            } else {
+                                // TODO return streaming server port
+                            }
+                            bResp.putSerializable("exception", exception);
+                            resp.setData(bResp);
+                            msg.replyTo.send(resp);
+                        } catch (RemoteException e) {
+                            Toast.makeText(AccountManagerService.this, e.getMessage(), Toast.LENGTH_SHORT).show();
+                            e.printStackTrace();
+                        }
+                    }
+
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
+    interface INetworkInterface {
+
+        Object handleGetRequest(GetMethod get) throws IOException;
+        Object handlePostRequest(PostMethod post) throws IOException;
+        Object handlePutRequest(PutMethod put) throws IOException;
+    }
+
+    private NameValuePair[] convertMapToNVP(Map<String, String> map) {
+        NameValuePair[] nvp = new NameValuePair[map.size()];
+        int i = 0;
+        for (String key : map.keySet()) {
+            nvp[i] = new NameValuePair(key, map.get(key));
+            i++;
+        }
+        return nvp;
+    }
+
+    public class PlainRequest implements INetworkInterface {
+
+        @Override
+        public Object handleGetRequest(GetMethod get) throws IOException {
+            return get.getResponseBody();
+        }
+
+        @Override
+        public Object handlePostRequest(PostMethod post) throws IOException {
+            return true;
+        }
+
+        @Override
+        public Object handlePutRequest(PutMethod put) throws IOException {
+            return true;
+        }
+
+    }
+
+    public class StreamingRequest implements INetworkInterface {
+
+        int port;
+
+        public StreamingRequest(int port) {
+            this.port = port;
+        }
+
+        @Override
+        public Object handleGetRequest(GetMethod get) throws IOException {
+            Log.d(TAG, "handleGetRequest() called with: get = [" + get + "]");
+
+
+            Socket m_activity_socket = new Socket();
+            SocketAddress sockaddr = new InetSocketAddress("127.0.0.1", port);
+            m_activity_socket.connect(sockaddr, 5000);
+
+
+            if (m_activity_socket.isConnected()) {
+                InputStream in = get.getResponseBodyAsStream();
+                OutputStream out = m_activity_socket.getOutputStream();
+                // the header describes the frame data (type, width, height, length)
+                // frame width and heigth have been previously decoded
+                //byte[] header = new byte[16];
+                //build_header( 1, width, height, frameData.length, header, 1 );
+                //m_nos.write( header );  // message header
+
+                IOUtils.copy(in, out);
+                in.close();
+                out.close();
+            }
+            return null;
+        }
+
+        @Override
+        public Object handlePostRequest(PostMethod post) throws IOException {
+            return true;
+        }
+
+        @Override
+        public Object handlePutRequest(PutMethod put) throws IOException {
+            return true;
+        }
+    }
+
+
+    private void addNewAccount() {
+        Intent dialogIntent = new Intent(AccountManagerService.this, FileDisplayActivity.class);
+        dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        // TODO make the activity start the "add new account" dialog automatically...
+        startActivity(dialogIntent);
+    }
+}

+ 52 - 0
src/main/java/com/owncloud/android/ui/asynctasks/AsyncTaskHelper.java

@@ -0,0 +1,52 @@
+package com.owncloud.android.ui.asynctasks;
+
+/**
+ * Created by david on 27.06.17.
+ */
+
+import android.os.AsyncTask;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+
+import java.util.concurrent.Callable;
+
+/**
+ * Created by david on 27.06.17.
+ */
+
+public class AsyncTaskHelper {
+
+    @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
+    public static <T> T ExecuteBlockingRequest(Callable<T> callable) throws Exception {
+        GenericAsyncTaskWithCallable<T> at = new GenericAsyncTaskWithCallable<>(callable);
+
+        T result = at.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR).get();
+
+        if(at.exception != null) {
+            throw at.exception;
+        }
+
+        return result;
+    }
+
+    private static class GenericAsyncTaskWithCallable<T> extends AsyncTask<Void, Void, T> {
+
+        private Callable<T> callable;
+        private Exception exception;
+
+        GenericAsyncTaskWithCallable(Callable<T> callable) {
+            this.callable = callable;
+        }
+
+        @Override
+        protected T doInBackground(Void... params) {
+            try {
+                return callable.call();
+            } catch (Exception e) {
+                exception = e;
+                return null;
+            }
+        }
+    }
+
+}

+ 11 - 0
src/main/java/de/luhmer/owncloud/accountimporter/helper/IThreadListener.java

@@ -0,0 +1,11 @@
+package de.luhmer.owncloud.accountimporter.helper;
+
+/**
+ * Created by david on 29.06.17.
+ */
+
+public interface IThreadListener {
+
+    void onThreadFinished(final Thread thread);
+
+}

+ 166 - 0
src/main/java/de/luhmer/owncloud/accountimporter/helper/InputStreamBinder.java

@@ -0,0 +1,166 @@
+package de.luhmer.owncloud.accountimporter.helper;
+
+import android.accounts.Account;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
+import com.owncloud.android.ui.asynctasks.AsyncTaskHelper;
+
+import org.apache.commons.httpclient.HttpMethodBase;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.methods.DeleteMethod;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.methods.PutMethod;
+import org.apache.commons.httpclient.methods.StringRequestEntity;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ * Created by david on 29.06.17.
+ *
+ * More information here: https://github.com/abeluck/android-streams-ipc
+ */
+
+public class InputStreamBinder extends IInputStreamService.Stub {
+    private final static String TAG = "InputStreamBinder";
+
+    private Context context;
+    public InputStreamBinder(Context ctxt) {
+        this.context = ctxt;
+    }
+
+
+    private NameValuePair[] convertMapToNVP(Map<String, String> map) {
+        NameValuePair[] nvp = new NameValuePair[map.size()];
+        int i = 0;
+        for (String key : map.keySet()) {
+            nvp[i] = new NameValuePair(key, map.get(key));
+            i++;
+        }
+        return nvp;
+    }
+
+
+    public ParcelFileDescriptor performNextcloudRequest(ParcelFileDescriptor input) {
+        // read the input
+        final InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
+
+        try {
+            final NextcloudRequest request = deserializeObjectAndCloseStream(is);
+
+            InputStream resultStream = processRequest(request);
+            try {
+                return ParcelFileDescriptorUtil.pipeFrom(resultStream, new IThreadListener() {
+                    @Override
+                    public void onThreadFinished(Thread thread) {
+                        Log.d(TAG, "Done sending result");
+                    }
+                });
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        } catch (IOException e) {
+            Log.d(TAG, "Test #1 failed");
+            e.printStackTrace();
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+
+    private <T> T deserializeObjectAndCloseStream(InputStream is) throws IOException, ClassNotFoundException {
+        ObjectInputStream ois = new ObjectInputStream(is);
+        T result = (T) ois.readObject();
+        is.close();
+        ois.close();
+        return result;
+    }
+
+    private InputStream processRequest(final NextcloudRequest request) {
+        try {
+            return AsyncTaskHelper.ExecuteBlockingRequest(new Callable<InputStream>() {
+                @Override
+                public InputStream call() throws Exception {
+                    Account account = AccountUtils.getOwnCloudAccountByName(context, request.accountName); // TODO handle case that account is not found!
+                    OwnCloudAccount ocAccount = new OwnCloudAccount(account, context);
+                    OwnCloudClient client = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, context);
+
+                    //OwnCloudVersion version = AccountUtils.getServerVersion(account);
+                    //client.setOwnCloudVersion(version);
+
+                    // TODO do some checks if url is correct!! (prevent ../ in url etc..
+                    request.url = client.getBaseUri() + request.url;
+
+                    //AccountManagerService.INetworkInterface network = (stream) ? new AccountManagerService.StreamingRequest(port) : new AccountManagerService.PlainRequest();
+
+                    HttpMethodBase method = null;
+
+                    switch (request.method) {
+                        case "GET":
+                            method = new GetMethod(request.url);
+                            break;
+
+                        case "POST":
+                            method = new PostMethod(request.url);
+                            if (request.requestBody != null) {
+                                StringRequestEntity requestEntity = new StringRequestEntity(
+                                        request.requestBody,
+                                        "application/json",
+                                        "UTF-8");
+                                ((PostMethod) method).setRequestEntity(requestEntity);
+                            }
+                            break;
+
+                        case "PUT":
+                            method = new PutMethod(request.url);
+                            if (request.requestBody != null) {
+                                StringRequestEntity requestEntity = new StringRequestEntity(
+                                        request.requestBody,
+                                        "application/json",
+                                        "UTF-8");
+                                ((PutMethod) method).setRequestEntity(requestEntity);
+                            }
+                            break;
+
+                        case "DELETE":
+                            method = new DeleteMethod(request.url);
+                            break;
+
+                        default:
+                            throw new Exception("Unexpected type!!");
+
+                    }
+
+                    method.setQueryString(convertMapToNVP(request.parameter));
+                    method.addRequestHeader("OCS-APIREQUEST", "true");
+
+                    //throw new Exception("Test!!!");
+
+                    int status = client.executeMethod(method);
+                    if (status == 200) {
+                        return method.getResponseBodyAsStream();
+                    } else {
+                        throw new Exception("Request returned code: " + status);
+                    }
+                }
+            });
+        } catch (Exception e) {
+            e.printStackTrace();
+            //TODO return exception to calling client app!
+            //return exceptionToInputStream(null);
+        }
+        return null;
+    }
+
+}

+ 77 - 0
src/main/java/de/luhmer/owncloud/accountimporter/helper/NextcloudRequest.java

@@ -0,0 +1,77 @@
+package de.luhmer.owncloud.accountimporter.helper;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by david on 28.06.17.
+ */
+
+public class NextcloudRequest implements Serializable {
+
+    static final long serialVersionUID = 215521212534236L; //assign a long value
+
+    public String method;
+    public Map<String, List<String>> header = new HashMap<>();
+    public Map<String, String> parameter = new HashMap<>();
+    public String requestBody;
+    public String url;
+    public String token;
+    public String accountName;
+
+    private NextcloudRequest() {
+
+    }
+
+    public static class Builder {
+        private NextcloudRequest ncr;
+
+        public Builder() {
+            ncr = new NextcloudRequest();
+        }
+
+        public NextcloudRequest build() {
+            return ncr;
+        }
+
+        public Builder setMethod(String method) {
+            ncr.method = method;
+            return this;
+        }
+
+        public Builder setHeader(Map<String, List<String>> header) {
+            ncr.header = header;
+            return this;
+        }
+
+        public Builder setParameter(HashMap<String, String> parameter) {
+            ncr.parameter = parameter;
+            return this;
+        }
+
+        public Builder setRequestBody(String requestBody) {
+            ncr.requestBody = requestBody;
+            return this;
+        }
+
+        public Builder setUrl(String url) {
+            ncr.url = url;
+            return this;
+        }
+
+        public Builder setToken(String token) {
+            ncr.token = token;
+            return this;
+        }
+
+        public Builder setAccountName(String accountName) {
+            ncr.accountName = accountName;
+            return this;
+        }
+    }
+
+
+
+}

+ 82 - 0
src/main/java/de/luhmer/owncloud/accountimporter/helper/ParcelFileDescriptorUtil.java

@@ -0,0 +1,82 @@
+package de.luhmer.owncloud.accountimporter.helper;
+
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class ParcelFileDescriptorUtil {
+
+    public static ParcelFileDescriptor pipeFrom(InputStream inputStream, IThreadListener listener)
+            throws IOException {
+        ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+        ParcelFileDescriptor readSide = pipe[0];
+        ParcelFileDescriptor writeSide = pipe[1];
+
+        // start the transfer thread
+        new TransferThread(inputStream, new ParcelFileDescriptor.AutoCloseOutputStream(writeSide),
+                listener)
+                .start();
+
+        return readSide;
+    }
+
+    public static ParcelFileDescriptor pipeTo(OutputStream outputStream, IThreadListener listener)
+            throws IOException {
+        ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+        ParcelFileDescriptor readSide = pipe[0];
+        ParcelFileDescriptor writeSide = pipe[1];
+
+        // start the transfer thread
+        new TransferThread(new ParcelFileDescriptor.AutoCloseInputStream(readSide), outputStream,
+                listener)
+                .start();
+
+        return writeSide;
+    }
+
+    static class TransferThread extends Thread {
+        final InputStream mIn;
+        final OutputStream mOut;
+        final IThreadListener mListener;
+
+        TransferThread(InputStream in, OutputStream out, IThreadListener listener) {
+            super("ParcelFileDescriptor Transfer Thread");
+            mIn = in;
+            mOut = out;
+            mListener = listener;
+            setDaemon(true);
+        }
+
+        @Override
+        public void run() {
+            byte[] buf = new byte[1024];
+            int len;
+
+            try {
+                while ((len = mIn.read(buf)) > 0) {
+                    mOut.write(buf, 0, len);
+                }
+                mOut.flush(); // just to be safe
+            } catch (IOException e) {
+                Log.e("TransferThread", "writing failed");
+                e.printStackTrace();
+            } finally {
+                try {
+                    mIn.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+                try {
+                    mOut.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (mListener != null)
+                mListener.onThreadFinished(this);
+        }
+    }
+}