From 6f25405ad4f4e6af8cc53f990927168dee645b06 Mon Sep 17 00:00:00 2001
From: Vavassor <copernicus-@hotmail.com>
Date: Thu, 9 Mar 2017 21:55:07 -0500
Subject: [PATCH] Volley is gone.

---
 app/build.gradle                              |   1 -
 .../keylesspalace/tusky/AccountFragment.java  |  15 --
 .../com/keylesspalace/tusky/BaseActivity.java |   8 +-
 .../keylesspalace/tusky/LoginActivity.java    | 134 +++++++-----------
 .../com/keylesspalace/tusky/MainActivity.java |  17 ---
 .../com/keylesspalace/tusky/MastodonAPI.java  |  21 ++-
 .../keylesspalace/tusky/MultipartRequest.java | 117 ---------------
 .../tusky/PullNotificationService.java        |  41 +++---
 .../keylesspalace/tusky/SplashActivity.java   |   1 -
 .../tusky/ViewThreadFragment.java             |   8 --
 .../keylesspalace/tusky/VolleySingleton.java  |  83 -----------
 .../tusky/entity/AccessToken.java             |  23 +++
 .../tusky/entity/AppCredentials.java          |  26 ++++
 .../com/keylesspalace/tusky/entity/Media.java |  15 ++
 .../tusky/entity/Relationship.java            |  15 ++
 .../tusky/entity/StatusContext.java           |  15 ++
 16 files changed, 183 insertions(+), 357 deletions(-)
 delete mode 100644 app/src/main/java/com/keylesspalace/tusky/MultipartRequest.java
 delete mode 100644 app/src/main/java/com/keylesspalace/tusky/VolleySingleton.java
 create mode 100644 app/src/main/java/com/keylesspalace/tusky/entity/AccessToken.java
 create mode 100644 app/src/main/java/com/keylesspalace/tusky/entity/AppCredentials.java

diff --git a/app/build.gradle b/app/build.gradle
index cad0b874a..01c3c2c36 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -29,7 +29,6 @@ dependencies {
     compile 'com.android.support:recyclerview-v7:25.2.0'
     compile 'com.android.support:support-v13:25.2.0'
     compile 'com.android.support:design:25.2.0'
-    compile 'com.android.volley:volley:1.0.0'
     compile 'com.squareup.picasso:picasso:2.5.2'
     compile 'com.pkmmte.view:circularimageview:1.1'
     compile 'com.github.peter9870:sparkbutton:master'
diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountFragment.java b/app/src/main/java/com/keylesspalace/tusky/AccountFragment.java
index fa0e67297..4afc04c1b 100644
--- a/app/src/main/java/com/keylesspalace/tusky/AccountFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/AccountFragment.java
@@ -17,7 +17,6 @@ package com.keylesspalace.tusky;
 
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
@@ -30,17 +29,10 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
-import com.android.volley.AuthFailureError;
-import com.android.volley.Request;
-import com.android.volley.Response;
-import com.android.volley.VolleyError;
-import com.android.volley.toolbox.StringRequest;
 import com.keylesspalace.tusky.entity.Account;
 import com.keylesspalace.tusky.entity.Relationship;
 
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import retrofit2.Call;
 import retrofit2.Callback;
@@ -57,8 +49,6 @@ public class AccountFragment extends Fragment implements AccountActionListener,
 
     private Type type;
     private String accountId;
-    private String domain;
-    private String accessToken;
     private RecyclerView recyclerView;
     private LinearLayoutManager layoutManager;
     private EndlessOnScrollListener scrollListener;
@@ -89,11 +79,6 @@ public class AccountFragment extends Fragment implements AccountActionListener,
         Bundle arguments = getArguments();
         type = Type.valueOf(arguments.getString("type"));
         accountId = arguments.getString("accountId");
-
-        SharedPreferences preferences = getContext().getSharedPreferences(
-                getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
-        domain = preferences.getString("domain", null);
-        accessToken = preferences.getString("accessToken", null);
         api = ((BaseActivity) getActivity()).mastodonAPI;
     }
 
diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
index fa529ed98..14ec4af38 100644
--- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java
@@ -96,9 +96,11 @@ public class BaseActivity extends AppCompatActivity {
                     public Response intercept(Chain chain) throws IOException {
                         Request originalRequest = chain.request();
 
-                        Request.Builder builder = originalRequest.newBuilder()
-                                .header("Authorization", String.format("Bearer %s", getAccessToken()));
-
+                        Request.Builder builder = originalRequest.newBuilder();
+                        String accessToken = getAccessToken();
+                        if (accessToken != null) {
+                            builder.header("Authorization", String.format("Bearer %s", accessToken));
+                        }
                         Request newRequest = builder.build();
 
                         return chain.proceed(newRequest);
diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java
index c83f12472..dca07f907 100644
--- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java
@@ -28,19 +28,19 @@ import android.widget.Button;
 import android.widget.EditText;
 import android.widget.TextView;
 
-import com.android.volley.Request;
-import com.android.volley.Response;
-import com.android.volley.VolleyError;
-import com.android.volley.toolbox.JsonObjectRequest;
-
-import org.json.JSONException;
-import org.json.JSONObject;
+import com.keylesspalace.tusky.entity.AccessToken;
+import com.keylesspalace.tusky.entity.AppCredentials;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
 public class LoginActivity extends BaseActivity {
-    private static final String TAG = "LoginActivity";
     private static String OAUTH_SCOPES = "read write follow";
 
     private SharedPreferences preferences;
@@ -112,50 +112,33 @@ public class LoginActivity extends BaseActivity {
             clientSecret = prefClientSecret;
             redirectUserToAuthorizeAndLogin();
         } else {
-            String endpoint = getString(R.string.endpoint_apps);
-            String url = "https://" + domain + endpoint;
-            JSONObject parameters = new JSONObject();
-            try {
-                parameters.put("client_name", getString(R.string.app_name));
-                parameters.put("redirect_uris", getOauthRedirectUri());
-                parameters.put("scopes", OAUTH_SCOPES);
-                parameters.put("website", getString(R.string.app_website));
-            } catch (JSONException e) {
-                Log.e(TAG, "Unable to build the form data for the authentication request.");
-                return;
-            }
-            JsonObjectRequest request = new JsonObjectRequest(
-                    Request.Method.POST, url, parameters,
-                    new Response.Listener<JSONObject>() {
-                        @Override
-                        public void onResponse(JSONObject response) {
-                            String obtainedClientId;
-                            String obtainedClientSecret;
-                            try {
-                                obtainedClientId = response.getString("client_id");
-                                obtainedClientSecret = response.getString("client_secret");
-                            } catch (JSONException e) {
-                                Log.e(TAG, "Couldn't get data from the authentication response.");
-                                return;
-                            }
-                            clientId = obtainedClientId;
-                            clientSecret = obtainedClientSecret;
-                            SharedPreferences.Editor editor = preferences.edit();
-                            editor.putString(domain + "/client_id", clientId);
-                            editor.putString(domain + "/client_secret", clientSecret);
-                            editor.apply();
-                            redirectUserToAuthorizeAndLogin();
-                        }
-                    }, new Response.ErrorListener() {
-                        @Override
-                        public void onErrorResponse(VolleyError error) {
-                            editText.setError(
-                                    "This app could not obtain authentication from that server " +
-                                    "instance.");
-                            error.printStackTrace();
-                        }
-                    });
-            VolleySingleton.getInstance(this).addToRequestQueue(request);
+            Callback<AppCredentials> callback = new Callback<AppCredentials>() {
+                @Override
+                public void onResponse(Call<AppCredentials> call, Response<AppCredentials> response) {
+                    AppCredentials credentials = response.body();
+                    clientId = credentials.clientId;
+                    clientSecret = credentials.clientSecret;
+                    SharedPreferences.Editor editor = preferences.edit();
+                    editor.putString(domain + "/client_id", clientId);
+                    editor.putString(domain + "/client_secret", clientSecret);
+                    editor.apply();
+                    redirectUserToAuthorizeAndLogin();
+                }
+
+                @Override
+                public void onFailure(Call<AppCredentials> call, Throwable t) {
+                    editText.setError(
+                            "This app could not obtain authentication from that server " +
+                            "instance.");
+                    t.printStackTrace();
+                }
+            };
+
+            List<String> redirectUris = new ArrayList<>();
+            redirectUris.add(getOauthRedirectUri());
+            mastodonAPI.authenticateApp(getString(R.string.app_name), redirectUris, OAUTH_SCOPES,
+                    getString(R.string.app_website)).enqueue(callback);
+
         }
     }
 
@@ -251,40 +234,19 @@ public class LoginActivity extends BaseActivity {
                 clientSecret = preferences.getString("clientSecret", null);
                 /* Since authorization has succeeded, the final step to log in is to exchange
                  * the authorization code for an access token. */
-                JSONObject parameters = new JSONObject();
-                try {
-                    parameters.put("client_id", clientId);
-                    parameters.put("client_secret", clientSecret);
-                    parameters.put("redirect_uri", redirectUri);
-                    parameters.put("code", code);
-                    parameters.put("grant_type", "authorization_code");
-                } catch (JSONException e) {
-                    errorText.setText(e.getMessage());
-                    return;
-                }
-                String endpoint = getString(R.string.endpoint_token);
-                String url = "https://" + domain + endpoint;
-                JsonObjectRequest request = new JsonObjectRequest(
-                    Request.Method.POST, url, parameters,
-                    new Response.Listener<JSONObject>() {
-                        @Override
-                        public void onResponse(JSONObject response) {
-                            String accessToken;
-                            try {
-                                accessToken = response.getString("access_token");
-                            } catch(JSONException e) {
-                                editText.setError(e.getMessage());
-                                return;
-                            }
-                            onLoginSuccess(accessToken);
-                        }
-                    }, new Response.ErrorListener() {
-                        @Override
-                        public void onErrorResponse(VolleyError error) {
-                            editText.setError(error.getMessage());
-                        }
-                    });
-                VolleySingleton.getInstance(this).addToRequestQueue(request);
+                Callback<AccessToken> callback = new Callback<AccessToken>() {
+                    @Override
+                    public void onResponse(Call<AccessToken> call, Response<AccessToken> response) {
+                        onLoginSuccess(response.body().accessToken);
+                    }
+
+                    @Override
+                    public void onFailure(Call<AccessToken> call, Throwable t) {
+                        editText.setError(t.getMessage());
+                    }
+                };
+                mastodonAPI.fetchOAuthToken(clientId, clientSecret, redirectUri, code,
+                        "authorization_code").enqueue(callback);
             } else if (error != null) {
                 /* Authorization failed. Put the error response where the user can read it and they
                  * can try again. */
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
index d027952ff..3d886cef1 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
@@ -22,27 +22,16 @@ import android.content.Intent;
 import android.content.SharedPreferences;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.Build;
 import android.os.SystemClock;
 import android.preference.PreferenceManager;
-import android.support.annotation.RequiresApi;
 import android.support.design.widget.FloatingActionButton;
 import android.support.design.widget.TabLayout;
 import android.support.v4.view.ViewPager;
 import android.os.Bundle;
 import android.support.v7.widget.Toolbar;
-import android.transition.Slide;
-import android.transition.TransitionInflater;
-import android.view.Menu;
-import android.view.MenuItem;
 import android.view.View;
 import android.widget.ImageView;
 
-import com.android.volley.AuthFailureError;
-import com.android.volley.Request;
-import com.android.volley.Response;
-import com.android.volley.VolleyError;
-import com.android.volley.toolbox.JsonObjectRequest;
 import com.keylesspalace.tusky.entity.Account;
 import com.mikepenz.materialdrawer.AccountHeader;
 import com.mikepenz.materialdrawer.AccountHeaderBuilder;
@@ -50,18 +39,12 @@ import com.mikepenz.materialdrawer.Drawer;
 import com.mikepenz.materialdrawer.DrawerBuilder;
 import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
 import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
-import com.mikepenz.materialdrawer.model.SecondaryDrawerItem;
 import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
 import com.mikepenz.materialdrawer.model.interfaces.IProfile;
 import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader;
 import com.mikepenz.materialdrawer.util.DrawerImageLoader;
 import com.squareup.picasso.Picasso;
 
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.HashMap;
-import java.util.Map;
 import java.util.Stack;
 
 import retrofit2.Call;
diff --git a/app/src/main/java/com/keylesspalace/tusky/MastodonAPI.java b/app/src/main/java/com/keylesspalace/tusky/MastodonAPI.java
index e6a78016d..51d8ec052 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MastodonAPI.java
+++ b/app/src/main/java/com/keylesspalace/tusky/MastodonAPI.java
@@ -1,6 +1,8 @@
 package com.keylesspalace.tusky;
 
+import com.keylesspalace.tusky.entity.AccessToken;
 import com.keylesspalace.tusky.entity.Account;
+import com.keylesspalace.tusky.entity.AppCredentials;
 import com.keylesspalace.tusky.entity.Media;
 import com.keylesspalace.tusky.entity.Notification;
 import com.keylesspalace.tusky.entity.Relationship;
@@ -10,7 +12,6 @@ import com.keylesspalace.tusky.entity.StatusContext;
 import java.util.List;
 
 import okhttp3.MultipartBody;
-import okhttp3.RequestBody;
 import okhttp3.ResponseBody;
 import retrofit2.Call;
 import retrofit2.http.DELETE;
@@ -167,4 +168,22 @@ public interface MastodonAPI {
     @FormUrlEncoded
     @POST("api/v1/reports")
     Call<ResponseBody> report(@Field("account_id") String accountId, @Field("status_ids[]") List<String> statusIds, @Field("comment") String comment);
+
+    @FormUrlEncoded
+    @POST("api/v1/apps")
+    Call<AppCredentials> authenticateApp(
+            @Field("client_name") String clientName,
+            @Field("redirect_uris[]") List<String> redirectUris,
+            @Field("scopes") String scopes,
+            @Field("website") String website);
+
+    @FormUrlEncoded
+    @POST("oauth/token")
+    Call<AccessToken> fetchOAuthToken(
+            @Field("client_id") String clientId,
+            @Field("client_secret") String clientSecret,
+            @Field("redirect_uri") String redirectUri,
+            @Field("code") String code,
+            @Field("grant_type") String grantType
+    );
 }
diff --git a/app/src/main/java/com/keylesspalace/tusky/MultipartRequest.java b/app/src/main/java/com/keylesspalace/tusky/MultipartRequest.java
deleted file mode 100644
index 41dd05e73..000000000
--- a/app/src/main/java/com/keylesspalace/tusky/MultipartRequest.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/* Copyright 2017 Andrew Dawson
- *
- * This file is part of Tusky.
- *
- * Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
- * General Public License as published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * Tusky 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 Tusky. If not, see
- * <http://www.gnu.org/licenses/>. */
-
-package com.keylesspalace.tusky;
-
-import com.android.volley.NetworkResponse;
-import com.android.volley.ParseError;
-import com.android.volley.Request;
-import com.android.volley.Response;
-import com.android.volley.toolbox.HttpHeaderParser;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-
-class MultipartRequest extends Request<JSONObject> {
-    private static final String CHARSET = "utf-8";
-    private final String boundary = "something-" + System.currentTimeMillis();
-
-    private JSONObject parameters;
-    private Response.Listener<JSONObject> listener;
-
-    MultipartRequest(int method, String url, JSONObject parameters,
-            Response.Listener<JSONObject> listener, Response.ErrorListener errorListener) {
-        super(method, url, errorListener);
-        this.parameters = parameters;
-        this.listener = listener;
-    }
-
-    @Override
-    public String getBodyContentType() {
-        return "multipart/form-data;boundary=" + boundary;
-    }
-
-    @Override
-    public byte[] getBody() {
-        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
-        DataOutputStream stream = new DataOutputStream(byteStream);
-        try {
-            // Write the JSON parameters first.
-            if (parameters != null) {
-                stream.writeBytes(String.format("--%s\r\n", boundary));
-                stream.writeBytes("Content-Disposition: form-data; name=\"parameters\"\r\n");
-                stream.writeBytes(String.format(
-                        "Content-Type: application/json; charset=%s\r\n", CHARSET));
-                stream.writeBytes("\r\n");
-                stream.writeBytes(parameters.toString());
-            }
-
-            // Write the binary data.
-            DataItem data = getData();
-            if (data != null) {
-                stream.writeBytes(String.format("--%s\r\n", boundary));
-                stream.writeBytes(String.format(
-                        "Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n",
-                        data.name, data.filename));
-                stream.writeBytes(String.format("Content-Type: %s\r\n", data.mimeType));
-                stream.writeBytes(String.format("Content-Length: %s\r\n",
-                        String.valueOf(data.content.length)));
-                stream.writeBytes("\r\n");
-                stream.write(data.content);
-            }
-
-            // Close the multipart form data.
-            stream.writeBytes(String.format("--%s--\r\n", boundary));
-
-            return byteStream.toByteArray();
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-        return null;
-    }
-
-    @Override
-    protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
-        try {
-            String jsonString = new String(response.data,
-                    HttpHeaderParser.parseCharset(response.headers));
-            return Response.success(new JSONObject(jsonString),
-                    HttpHeaderParser.parseCacheHeaders(response));
-        } catch (JSONException|UnsupportedEncodingException e) {
-            return Response.error(new ParseError(e));
-        }
-    }
-
-    @Override
-    protected void deliverResponse(JSONObject response) {
-        listener.onResponse(response);
-    }
-
-    public DataItem getData() {
-        return null;
-    }
-
-    static class DataItem {
-        String name;
-        String filename;
-        String mimeType;
-        byte[] content;
-    }
-}
diff --git a/app/src/main/java/com/keylesspalace/tusky/PullNotificationService.java b/app/src/main/java/com/keylesspalace/tusky/PullNotificationService.java
index ca586fec2..44fc636fe 100644
--- a/app/src/main/java/com/keylesspalace/tusky/PullNotificationService.java
+++ b/app/src/main/java/com/keylesspalace/tusky/PullNotificationService.java
@@ -20,6 +20,7 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
@@ -28,25 +29,16 @@ import android.support.v4.app.NotificationCompat;
 import android.support.v4.app.TaskStackBuilder;
 import android.text.Spanned;
 
-import com.android.volley.AuthFailureError;
-import com.android.volley.Response;
-import com.android.volley.VolleyError;
-import com.android.volley.toolbox.ImageRequest;
-import com.android.volley.toolbox.JsonArrayRequest;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.keylesspalace.tusky.entity.*;
 import com.keylesspalace.tusky.entity.Notification;
-
-import org.json.JSONArray;
-import org.json.JSONException;
+import com.squareup.picasso.Picasso;
+import com.squareup.picasso.Target;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import okhttp3.Interceptor;
 import okhttp3.OkHttpClient;
@@ -58,18 +50,12 @@ import retrofit2.converter.gson.GsonConverterFactory;
 
 public class PullNotificationService extends IntentService {
     static final int NOTIFY_ID = 6; // This is an arbitrary number.
-    private static final String TAG = "PullNotifications"; // logging tag and Volley request tag
+    private static final String TAG = "PullNotifications"; // logging tag
 
     public PullNotificationService() {
         super("Tusky Pull Notification Service");
     }
 
-    @Override
-    public void onDestroy() {
-        VolleySingleton.getInstance(this).cancelAll(TAG);
-        super.onDestroy();
-    }
-
     @Override
     protected void onHandleIntent(Intent intent) {
         SharedPreferences preferences = getSharedPreferences(
@@ -173,18 +159,23 @@ public class PullNotificationService extends IntentService {
 
     private void loadAvatar(final List<MentionResult> mentions, String url) {
         if (url != null) {
-            ImageRequest request = new ImageRequest(url, new Response.Listener<Bitmap>() {
+            Target target = new Target() {
                 @Override
-                public void onResponse(Bitmap response) {
-                    updateNotification(mentions, response);
+                public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
+                    updateNotification(mentions, bitmap);
                 }
-            }, 0, 0, null, null, new Response.ErrorListener() {
+
                 @Override
-                public void onErrorResponse(VolleyError error) {
+                public void onBitmapFailed(Drawable errorDrawable) {
                     updateNotification(mentions, null);
                 }
-            });
-            VolleySingleton.getInstance(this).addToRequestQueue(request);
+
+                @Override
+                public void onPrepareLoad(Drawable placeHolderDrawable) {}
+            };
+            Picasso.with(this)
+                    .load(url)
+                    .into(target);
         } else {
             updateNotification(mentions, null);
         }
diff --git a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.java b/app/src/main/java/com/keylesspalace/tusky/SplashActivity.java
index c3e38c035..d35a3ce17 100644
--- a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/SplashActivity.java
@@ -37,7 +37,6 @@ public class SplashActivity extends Activity {
         String domain = preferences.getString("domain", null);
         String accessToken = preferences.getString("accessToken", null);
 
-
         final Intent intent;
 
         if (domain != null && accessToken != null) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java
index f615eb048..a83c35e2d 100644
--- a/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java
@@ -27,17 +27,9 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
-import com.android.volley.Request;
-import com.android.volley.Response;
-import com.android.volley.VolleyError;
 import com.keylesspalace.tusky.entity.Status;
 import com.keylesspalace.tusky.entity.StatusContext;
 
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.List;
-
 import retrofit2.Call;
 import retrofit2.Callback;
 
diff --git a/app/src/main/java/com/keylesspalace/tusky/VolleySingleton.java b/app/src/main/java/com/keylesspalace/tusky/VolleySingleton.java
deleted file mode 100644
index c1807b981..000000000
--- a/app/src/main/java/com/keylesspalace/tusky/VolleySingleton.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/* Copyright 2017 Andrew Dawson
- *
- * This file is part of Tusky.
- *
- * Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
- * General Public License as published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * Tusky 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 Tusky. If not, see
- * <http://www.gnu.org/licenses/>. */
-
-package com.keylesspalace.tusky;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.support.v4.util.LruCache;
-
-import com.android.volley.Request;
-import com.android.volley.RequestQueue;
-import com.android.volley.toolbox.ImageLoader;
-import com.android.volley.toolbox.Volley;
-
-import java.lang.ref.WeakReference;
-
-public class VolleySingleton {
-    private static VolleySingleton instance;
-    private RequestQueue requestQueue;
-    private ImageLoader imageLoader;
-    /* This is a weak reference to account for the case where it might be held onto beyond the
-     * lifetime of the Activity/Fragment/Service, so it can be cleaned up. */
-    private static WeakReference<Context> context;
-
-    private VolleySingleton(Context context) {
-        VolleySingleton.context = new WeakReference<>(context);
-        requestQueue = getRequestQueue();
-        imageLoader = new ImageLoader(requestQueue,
-                new ImageLoader.ImageCache() {
-                    private final LruCache<String, Bitmap> cache = new LruCache<>(20);
-
-                    @Override
-                    public Bitmap getBitmap(String url) {
-                        return cache.get(url);
-                    }
-
-                    @Override
-                    public void putBitmap(String url, Bitmap bitmap) {
-                        cache.put(url, bitmap);
-                    }
-                });
-    }
-
-    public static synchronized VolleySingleton getInstance(Context context) {
-        if (instance == null) {
-            instance = new VolleySingleton(context);
-        }
-        return instance;
-    }
-
-    private RequestQueue getRequestQueue() {
-        if (requestQueue == null) {
-            /* getApplicationContext() is key, it keeps you from leaking the
-             * Activity or BroadcastReceiver if someone passes one in. */
-            requestQueue= Volley.newRequestQueue(context.get().getApplicationContext());
-        }
-        return requestQueue;
-    }
-
-    <T> void addToRequestQueue(Request<T> request) {
-        getRequestQueue().add(request);
-    }
-
-    void cancelAll(String tag) {
-        getRequestQueue().cancelAll(tag);
-    }
-
-    ImageLoader getImageLoader() {
-        return imageLoader;
-    }
-}
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/AccessToken.java b/app/src/main/java/com/keylesspalace/tusky/entity/AccessToken.java
new file mode 100644
index 000000000..422369a5d
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/AccessToken.java
@@ -0,0 +1,23 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is part of Tusky.
+ *
+ * Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tusky 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 Tusky. If not, see
+ * <http://www.gnu.org/licenses/>. */
+
+package com.keylesspalace.tusky.entity;
+
+import com.google.gson.annotations.SerializedName;
+
+public class AccessToken {
+    @SerializedName("access_token")
+    public String accessToken;
+}
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/AppCredentials.java b/app/src/main/java/com/keylesspalace/tusky/entity/AppCredentials.java
new file mode 100644
index 000000000..30d296fd9
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/AppCredentials.java
@@ -0,0 +1,26 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is part of Tusky.
+ *
+ * Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tusky 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 Tusky. If not, see
+ * <http://www.gnu.org/licenses/>. */
+
+package com.keylesspalace.tusky.entity;
+
+import com.google.gson.annotations.SerializedName;
+
+public class AppCredentials {
+    @SerializedName("client_id")
+    public String clientId;
+
+    @SerializedName("client_secret")
+    public String clientSecret;
+}
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Media.java b/app/src/main/java/com/keylesspalace/tusky/entity/Media.java
index 1db867fb0..149ab0986 100644
--- a/app/src/main/java/com/keylesspalace/tusky/entity/Media.java
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/Media.java
@@ -1,3 +1,18 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is part of Tusky.
+ *
+ * Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tusky 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 Tusky. If not, see
+ * <http://www.gnu.org/licenses/>. */
+
 package com.keylesspalace.tusky.entity;
 
 import com.google.gson.annotations.SerializedName;
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.java b/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.java
index d08744053..b93db03b1 100644
--- a/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.java
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.java
@@ -1,3 +1,18 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is part of Tusky.
+ *
+ * Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tusky 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 Tusky. If not, see
+ * <http://www.gnu.org/licenses/>. */
+
 package com.keylesspalace.tusky.entity;
 
 import com.google.gson.annotations.SerializedName;
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/StatusContext.java b/app/src/main/java/com/keylesspalace/tusky/entity/StatusContext.java
index 338b55037..a043ca348 100644
--- a/app/src/main/java/com/keylesspalace/tusky/entity/StatusContext.java
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/StatusContext.java
@@ -1,3 +1,18 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is part of Tusky.
+ *
+ * Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tusky 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 Tusky. If not, see
+ * <http://www.gnu.org/licenses/>. */
+
 package com.keylesspalace.tusky.entity;
 
 import java.util.List;