// Path: AndroidApp/src/ca/psiphon/ploggy/Log.java
// public class Log {
// 
//     private static final String LOG_TAG = "Log";
// 
//     public static class Entry {
//         public final Date mTimestamp;
//         public final String mTag;
//         public final String mMessage;
// 
//         public Entry(String tag, String message) {
//             mTimestamp = new Date();
//             mTag = tag;
//             mMessage = message;
//         }
//     }
// 
//     public interface Observer {
//         void onUpdatedRecentEntries();
//     }
// 
//     private static final int MAX_RECENT_ENTRIES = 500;
// 
//     private static ArrayList<Entry> mRecentEntries;
//     private static ArrayList<Observer> mObservers;
//     private static Handler mHandler;
// 
//     // TODO: explicit singleton?
// 
//     public synchronized static void initialize() {
//         mRecentEntries = new ArrayList<Entry>();
//         mObservers = new ArrayList<Observer>();
//         mHandler = new Handler();
//     }
// 
//     public synchronized static void addEntry(String tag, String message) {
//         if (message == null) {
//             message = "(null)";
//         }
// 
//         Entry entry = new Entry(tag, message);
// 
//         // Update the in-memory entry list on the UI thread (also
//         // notifies any ListView adapters subscribed to that list)
//         postAddEntry(entry);
// 
//         // Temporary
//         android.util.Log.e("Ploggy", tag + " " + message);
//     }
// 
//     public synchronized static int getRecentEntryCount() {
//         return mRecentEntries.size();
//     }
// 
//     public synchronized static Entry getRecentEntry(int index) {
//         return mRecentEntries.get(index);
//     }
// 
//     public synchronized static void registerObserver(Observer observer) {
//         if (!mObservers.contains(observer)) {
//             mObservers.add(observer);
//         }
//     }
// 
//     public synchronized static void unregisterObserver(Observer observer) {
//         mObservers.remove(observer);
//     }
// 
//     public synchronized static void composeEmail(Context context) {
//         // TODO: temporary feature for debugging prototype -- will compromise unlinkability
//         try {
//             StringBuilder body = new StringBuilder();
//             for (Entry entry : mRecentEntries) {
//                 body.append(entry.mTimestamp);
//                 body.append(" ");
//                 body.append(entry.mTag);
//                 body.append(": ");
//                 body.append(entry.mMessage);
//                 body.append("\n");
//             }
// 
//             Intent intent = new Intent(Intent.ACTION_SEND);
//             intent.setType("message/rfc822");
//             intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"feedback+ploggy@psiphon.ca"});
//             intent.putExtra(Intent.EXTRA_SUBJECT, "Ploggy Logs");
//             intent.putExtra(Intent.EXTRA_TEXT, body.toString());
//             context.startActivity(intent);
//         } catch (ActivityNotFoundException e) {
//             Log.addEntry(LOG_TAG, e.getMessage());
//             Log.addEntry(LOG_TAG, "compose log email failed");
//         }
//     }
// 
//     private static void postAddEntry(Entry entry) {
//         final Entry finalEntry = entry;
//         mHandler.post(
//             new Runnable() {
//                 @Override
//                 public void run() {
//                     mRecentEntries.add(finalEntry);
//                     while (mRecentEntries.size() > MAX_RECENT_ENTRIES) {
//                         mRecentEntries.remove(0);
//                     }
//                     for (Observer observer : mObservers) {
//                         observer.onUpdatedRecentEntries();
//                     }
//                 }
//             });
//     }
// }


import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.SequenceInputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimeZone;
import ca.psiphon.ploggy.Log;

        myThread = new Thread(new Runnable() {
            @Override
            public void run() {
                do {
                    try {
                        final Socket finalAccept = myServerSocket.accept();
                        registerConnection(finalAccept);
                        finalAccept.setSoTimeout(getReadTimeout());
                        final InputStream inputStream = finalAccept.getInputStream();
                        if (inputStream == null) {
                            safeClose(finalAccept);
                            unRegisterConnection(finalAccept);
                        } else {
                            asyncRunner.exec(new Runnable() {
                                @Override
                                public void run() {
                                    OutputStream outputStream = null;
                                    try {
                                        outputStream = finalAccept.getOutputStream();
                                        TempFileManager tempFileManager = tempFileManagerFactory.create();
                                        HTTPSession session = new HTTPSession(finalAccept, tempFileManager, inputStream, outputStream);
                                        while (!finalAccept.isClosed()) {
                                            session.execute();
                                        }
                                    } catch (Exception e) {
                                        // When the socket is closed by the client, we throw our own SocketException
                                        // to break the  "keep alive" loop above.
                                        if (!(e instanceof SocketException && "NanoHttpd Shutdown".equals(e.getMessage()))) {
                                            // ==== ploggy ====
// Path: AndroidApp/src/ca/psiphon/ploggy/Log.java
// public class Log {
// 
//     private static final String LOG_TAG = "Log";
// 
//     public static class Entry {
//         public final Date mTimestamp;
//         public final String mTag;
//         public final String mMessage;
// 
//         public Entry(String tag, String message) {
//             mTimestamp = new Date();
//             mTag = tag;
//             mMessage = message;
//         }
//     }
// 
//     public interface Observer {
//         void onUpdatedRecentEntries();
//     }
// 
//     private static final int MAX_RECENT_ENTRIES = 500;
// 
//     private static ArrayList<Entry> mRecentEntries;
//     private static ArrayList<Observer> mObservers;
//     private static Handler mHandler;
// 
//     // TODO: explicit singleton?
// 
//     public synchronized static void initialize() {
//         mRecentEntries = new ArrayList<Entry>();
//         mObservers = new ArrayList<Observer>();
//         mHandler = new Handler();
//     }
// 
//     public synchronized static void addEntry(String tag, String message) {
//         if (message == null) {
//             message = "(null)";
//         }
// 
//         Entry entry = new Entry(tag, message);
// 
//         // Update the in-memory entry list on the UI thread (also
//         // notifies any ListView adapters subscribed to that list)
//         postAddEntry(entry);
// 
//         // Temporary
//         android.util.Log.e("Ploggy", tag + " " + message);
//     }
// 
//     public synchronized static int getRecentEntryCount() {
//         return mRecentEntries.size();
//     }
// 
//     public synchronized static Entry getRecentEntry(int index) {
//         return mRecentEntries.get(index);
//     }
// 
//     public synchronized static void registerObserver(Observer observer) {
//         if (!mObservers.contains(observer)) {
//             mObservers.add(observer);
//         }
//     }
// 
//     public synchronized static void unregisterObserver(Observer observer) {
//         mObservers.remove(observer);
//     }
// 
//     public synchronized static void composeEmail(Context context) {
//         // TODO: temporary feature for debugging prototype -- will compromise unlinkability
//         try {
//             StringBuilder body = new StringBuilder();
//             for (Entry entry : mRecentEntries) {
//                 body.append(entry.mTimestamp);
//                 body.append(" ");
//                 body.append(entry.mTag);
//                 body.append(": ");
//                 body.append(entry.mMessage);
//                 body.append("\n");
//             }
// 
//             Intent intent = new Intent(Intent.ACTION_SEND);
//             intent.setType("message/rfc822");
//             intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"feedback+ploggy@psiphon.ca"});
//             intent.putExtra(Intent.EXTRA_SUBJECT, "Ploggy Logs");
//             intent.putExtra(Intent.EXTRA_TEXT, body.toString());
//             context.startActivity(intent);
//         } catch (ActivityNotFoundException e) {
//             Log.addEntry(LOG_TAG, e.getMessage());
//             Log.addEntry(LOG_TAG, "compose log email failed");
//         }
//     }
// 
//     private static void postAddEntry(Entry entry) {
//         final Entry finalEntry = entry;
//         mHandler.post(
//             new Runnable() {
//                 @Override
//                 public void run() {
//                     mRecentEntries.add(finalEntry);
//                     while (mRecentEntries.size() > MAX_RECENT_ENTRIES) {
//                         mRecentEntries.remove(0);
//                     }
//                     for (Observer observer : mObservers) {
//                         observer.onUpdatedRecentEntries();
//                     }
//                 }
//             });
//     }
// }


// Path: AndroidApp/src/fi/iki/elonen/NanoHTTPD.java
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.SequenceInputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimeZone;
import ca.psiphon.ploggy.Log;


        myThread = new Thread(new Runnable() {
            @Override
            public void run() {
                do {
                    try {
                        final Socket finalAccept = myServerSocket.accept();
                        registerConnection(finalAccept);
                        finalAccept.setSoTimeout(getReadTimeout());
                        final InputStream inputStream = finalAccept.getInputStream();
                        if (inputStream == null) {
                            safeClose(finalAccept);
                            unRegisterConnection(finalAccept);
                        } else {
                            asyncRunner.exec(new Runnable() {
                                @Override
                                public void run() {
                                    OutputStream outputStream = null;
                                    try {
                                        outputStream = finalAccept.getOutputStream();
                                        TempFileManager tempFileManager = tempFileManagerFactory.create();
                                        HTTPSession session = new HTTPSession(finalAccept, tempFileManager, inputStream, outputStream);
                                        while (!finalAccept.isClosed()) {
                                            session.execute();
                                        }
                                    } catch (Exception e) {
                                        // When the socket is closed by the client, we throw our own SocketException
                                        // to break the  "keep alive" loop above.
                                        if (!(e instanceof SocketException && "NanoHttpd Shutdown".equals(e.getMessage()))) {
                                            // ==== ploggy ====
                                            Log.addEntry(LOG_TAG, e.getMessage());